Implementing Rails 3 template handlers - ruby-on-rails-3

It appears there's not much documentation on Rails template handlers. There's the included handlers, like RJS, ERB, and Builder, which offer some assistance.
I'm trying to implement my own, and I've succeeded, albeit with a bit of weird code, or possibly there's something I don't quite understand.
class MyHandler < ActionView::Template::Handler
def call(template)
template.source.inspect
end
end
So what's weird is that I have to call inspect, otherwise Rails will try to eval the string as Ruby code.
I was under the impression that that's what include ActionView::...::Compilable did (which I'm not including in my code).
Now, if I make my template "compilable" (by using the include... statement), it still looks for the call method instead of the compile method.
So could anyone explain to me a bit more about how this works?
Thanks!

I've just been going through this problem myself. Basically rails expects the renderer's .call method to return ruby code that will render your template. It then dynamically generates a method which runs this code, and injects it into a module.
The module has all of the url/application helpers included, which means they're in scope for the template.
So, in answer to your question the solution is for .call to return some ruby code that outputs your rendered template as a string, or for it to render ruby code that invokes your template engine.

Check out tilt and temple, I have learnt a lot about template engines reading their code.

Related

Rails 5.1.1 deprecation warning changed_attributes

I've just upgraded from Rails 5.0.0 to 5.1.1 and started getting a ton of deprecation warnings like this:
DEPRECATION WARNING: The behavior of changed_attributes inside of
after callbacks will be changing in the next version of Rails. The new
return value will reflect the behavior of calling the method after
save returned (e.g. the opposite of what it returns now). To
maintain the current behavior, use
saved_changes.transform_values(&:first) instead.
and this:
DEPRECATION WARNING: The behavior of attribute_changed? inside of
after callbacks will be changing in the next version of Rails. The new
return value will reflect the behavior of calling the method after
save returned (e.g. the opposite of what it returns now). To
maintain the current behavior, use saved_change_to_attribute?
instead.
I don't use those methods explicitely anywhere in my project and the warnings are pointing mostly at create and update calls on my models.
I believe it has something to do with my validations and after_update and after_create callbacks where I use confitions like if: { author_id_changed? } but I have no idea what to do with them.
I also believe the warning is related to this massive update to ActiveRecord.
Would appreciate any hand you can give with this.
UPD
This article helped alot!
Well, got around everything by running bundle update and updating the gems and also following this article and changing attribute_changed? calls in after_ callbacks (but not in before_ callbacks and validations) and switching from attribute_was to attribute_before_last_save.
For After callbacks, you can use saved_change_to_attribute?
For Before callbacks and validations, you can use will_save_change_to_attribute?
I hope this information will help!
I've done an upgrade to Rails 5.1.6 and have the same DEPRECATION warnings. If ever anyone still wants to solve this warning. Here are the steps I took:
Search all of your*_changed?
Changed this:
if name_changed?
...
if user_id_changed?
To this if it is inside after_* (after_save, after_commit, after_update, etc.) blocks:
if saved_change_to_name?
...
if saved_change_to_user_id?
AND to this if it is inside before_* (before_save, before_commit, before_update, etc.) blocks:
if will_save_change_to_name?
...
if will_save_change_to_user_id?
On my own opinion, this is quite tricky thing to change since we've been used to attribute_changed?. But change is good. The syntax also made more sense now.
You can change author_id_changed? to saved_change_to_author_id?

Undo model namespacing causes error

I had both namespaced controllers and models like this:
controller: Modules::Insurances
model: Modules::Insurances
This worked fine, but I want to remove the namespaced model back to normal, but keep the namespaced controller. I have removed all the references to "Modules::" in the models and also in all the activerecord queries in the controller to the model. When I visit the page, it returns this error:
Expected /***/app/models/modules/ins_insurance.rb to define Modules::InsInsurance
What should I do more to remove the namespaced model?
The root cause of the error is that rails is expecting the name of the class declared inside the file modules/ins_insurance.rb to be Modules::InsInsurance -- the filename must match the class name (after a little conversion thanks to the Inflector's underscore and classify methods: http://apidock.com/rails/ActiveSupport/Inflector/underscore). So name the file back to insurance.rb and you should be OK.
On a broader topic, I can only think you'll run into problems sooner or later by using a sub-directory called modules -- at the very least it's confusing between the Ruby keyword Module. If you're looking for a place to put general purpose code, use app/lib then require the file as needed. Namespacing is a fine thing to do, but you'll end up fighting with Rails unless you go with the flow. (Lesson learned the hard way ;-)

Make all ActionViewer helper methods available in a module

I am using ruby on rails 3. I have a model called Content. Within the controller, I extract some variables and pass them to the Viewer. In the Viewer, I would like to use of my self made functions so that I don't have too much logic and code in the Viewer. I defined these functions in the ContentsHelper module.
My functions defined in the ContentsHelper module are making use of ActionViewer helper functions, however these are not available in the ContentsHelper module. I am getting for example NoMethodError: undefined method content_tag' for #<Content:0x1025b9458> error.
How is it possible to make all the ActionViewer helper functions available?
Newbie.
following suggestion of Tim:
module ContentsHelper
include ActionView::Helpers
def bla
content_tag(:h2, self.title)
end
I can run the bla method as Content.bla in the console. This is perfect!
But when I replacec content_tag with image_tag, then I run into trouble and get this error:
NoMethodError: undefined method `config'
from /Users/sonat/.rvm/gems/ruby-1.8.7-p334/gems/activemodel-3.0.0/lib/active_model/attribute_methods.rb:364:in `method_missing'
from /Users/sonat/.rvm/gems/ruby-1.8.7-p334/gems/activerecord-3.0.0/lib/active_record/attribute_methods.rb:46:in `method_missing'
from /Users/sonat/.rvm/gems/ruby-1.8.7-p334/gems/activerecord-3.0.0/lib/active_record/attribute_methods.rb:44:in `send'
I can see that module that is responsible of the image_tag method is missing "something". I would appreciate if you guys tell me the general approach in finding what is missing and how I could solve the problem.
Looks like you're trying to do something odd like to use the helper method inside your model instead of inside your view. Helper methods are for views, not for models.
Added
Tim asked if I could provide some links to other resources, but really this is such basic stuff that I'm not aware of anything dealing with this. Mostly tutorials deal with unusual things like using helpers outside views.
So instead, here's some code:
app/helpers/contents_helper.rb
module ContentsHelper
def foo
content_tag :p, "Foo"
end
end
app/views/contents/index.html.erb
<h1>Listing Contents</h1>
<%= foo %>
The HTML output when going to /contents URL
<h1>Listing Contents</h1>
<p>Foo</p>
And that's as hard as it is.
content_tag:
Does this work?
module ContentsHelper
include ActionView::Helpers
end
image_tag:
Looks like you'll have to make your models know where the images are located by implementing a #path_to_image(image) method.
I've set up a Gist for us to mock out the features you need here:
https://gist.github.com/1015333
You should be able to download and run forNoNameNo.rb (which should run quietly without errors.)
That file has some examples of what's needed to make image_tag work.
further discussion:
Helpers are probably the weirdest part of Rails. They're declared at the controller level, but are only included at the view level, and rely on weird parts of the controller stack for their info; and yet, the info they are mainly concerned with presenting is the model info. So they're spread across all these weird concerns.
To deal with code-organizations issues arising from all of this, people have tended toward the Presenter pattern. (Also see rbates' version)
That said, if you'd prefer to stray off the beaten path in rails, it's normally really useful to read the source code.
If you're using textmate:
mate `gem which actionpack`../action_view
in Terminal.app should get your there. If you're using another editor/operating system you might have to navigate to whereever gem which actionpack tells you to go.

using helper methods in a view in rails 3

there is something that is disturbing me a bit about rails 3.
i am trying to upgrade my application from rails 2 to rails 3.
i followed all the steps necessary to do and things were working well for me till now except one thing:
i have a helper method that is defined differently in different helpers, for example it is defined in the application_helper in one way and the same method defined in a different way in the homepage_helper, and defined differently in another helper, let's say video_helper, in addition to that, it is used in a shared view that is used throughout the application, now if i am in the homepage, i want this function to get called from the homepage_helper and if i am in the video page, the function must be called from the video_helper, based on the concept of convention in rails, this worked perfectly in rails 2 but in rails 3, the function is always called from the last helper, alphabetically ordered, that is in the example, video_helper.
How can i fix this?
Kindly advice.
Appreciate all the help.
In rails 3 all helpers are always loaded.
You can either:
1) name your helpers differently.
2) Explicity define which helpers you want
3) Check the controller from the helpers to determine which helper to provide.
If I'm understanding your post right, you have something like
module ApplicationHelper
def error_messages
...
end
end
module VideoHelper
def error_messages
...
end
end
module HomepageHelper
def error_messages
...
end
end
And now you want to call error_messages from a specific helper and calling error_messages arbitrarily behaves differently from how it did in Rails 2. Am I right so far? If not, you can ignore the rest of this post =P
I just spent half an hour playing with the syntax and one of my projects in irb and the closest I could get was something along the lines of Posts::PostsHelper.helper_method, which works in irb if I 'include PostsHelper' first.
With that said, if you're willing to do something like Class::ClassHelper.helper_method, why not just rename "helper_method" so that it's unique to your ClassHelper? You're going to have to change each "helper_method" call to be specific to some helper anyways. I think this is a much cleaner solution

rails3 gem: acts_as_something

I'm trying to extract some common code into a gem.
I'm thinking that acts_as_somethingis a good strategy for simple re-use.
Is there a good tutorial that discusses this for rails3 gems? I've found several that discuss rails2 (such as http://guides.rubyonrails.org/plugins.html) but that is specific to rails2
here are some of the tutorials/blogs I've already read:
http://guides.rubyonrails.org/plugins.html
http://www.thoughtsincomputation.com/categories/coding-gems-for-rails-3-tutorials (this is an excellent start but doesn't cover the act_as_* issue or controllers)
thanks
UPDATE: I've added a blog post based on this answer, but with much more detail: http://thoughtsincomputation.com/posts/coding-an-acts_as-gem-for-rails-3
--
I'm not aware of another tutorial source off the top of my head, but here are some general tips.
Rails 3 makes use of a really useful feature called Railtie - see http://api.rubyonrails.org/classes/Rails/Railtie.html .
So, if I were implementing an acts_as_* gem, I'd start there. My railtie might look something like:
# lib/acts_as_awesome/railtie.rb
require 'rails'
require 'acts_as_awesome'
module ActsAsAwesome
class Railtie < Rails::Railtie
config.to_prepare do
ApplicationController.send(:extend, ActsAsAwesome::Hook)
end
end
end
and the ActsAsAwesome::Hook code:
# lib/acts_as_awesome/hook.rb
module ActsAsAwesome::Hook
def acts_as_awesome(*args)
options = args.extract_options!
# do the things that make the controller awesome.
include ActsAsAwesome::InstanceMethods
before_filter :an_awesome_filter
end
end
I feel the concepts here are sound and have used similar processes before. Basically, it would tell Rails to execute the to_prepare block once during production and before each request in development (we want that because ApplicationController will be reloaded at those times, potentially wiping out our hook method); and the hook is just that: it adds a hook to all controllers (or rather, all controllers that extend ApplicationController) to allow the user to introduce the real "Awesome" code into their controllers without otherwise affecting controllers that don't need it.
The #acts_as_awesome hook doesn't, in itself, convey the Awesome functionality. That's because not all controllers might need this functionality. Instead, the method is responsible for introducing the real awesome stuff, via the ActsAsAwesome::InstanceMethods module. This way, the user only gets the Awesome functionality if they explicitly call the acts_as_awesome method. It also adds a before filter to the controller to demonstrate that the code in this method would be evaluated exactly the same as if it were in the target controller class itself.
This technique should work exactly the same if you're targeting models instead of controllers: just inject your hook into ActiveRecord::Base. As AR:B is only loaded at Rails boot, you should probably be able to put that into an initializer (refer to the Railtie docs), but I reserve the right to be mistaken here.
A gotcha concerning the railtie: the documentation reads as if it should have been autodetected, but I often have problems with this. To get around it, simply require the railtie from your gem's main source file (in the above example, that would be lib/acts_as_awesome.rb).
You can see the entire ActsAsAwesome source in all its glory at my github account:
http://github.com/sinisterchipmunk/acts_as_awesome
I hope this is helpful. Your question was somewhat high-level so a high-level response is the best I can do.
-Colin MacKenzie IV
http://thoughtsincomputation.com
#sinisterchipmnk