ERB scope: instance variable vs locals - ruby-on-rails

Suppose I have a controller with an index action and I would like to pass some data into the action's view (index.html.erb).
Typically, the rails way is to do: #some_var = some_value.
Does the above add to the global scope in a sense that #some_var is now available everywhere (helpers, etc...)? If so, is it better to do something like locals: {some_var: some_value} instead?
What are the tradeoffs if any?

Yes, #some_var is "global" in the sense that it added to the view context that is shared by views and helpers. So your controller, view, partials, and helpers can all access the same #some_var.
Using instance variables like this is the Rails convention: they just appear "like magic" in your views and helpers with no additional code. For simplicity, I would recommend it for most projects, especially as you are starting out.
However as your Rails app grows, here are some other best practices to keep in mind:
Try to limit your controller to assigning only one instance variable. If you find yourself assigning many instance variables in a single controller action, that is usually a sign that your controller is trying to do too much.
Avoid using instance variables in helpers. This makes your helper methods harder to reuse in other contexts, because they depend on instance variables being set "just right" by the controller. This can be a source of bugs.

Yes, I think you've got it. I prefer local arguments like that too, even though you're right Rails suggests you use instance variables.
I think instance variables may be okay when they are in a full template (never in a partial), and especially when you only have one of them that has the same name as the controller or whatever.
But in general, I agree with you. There is no downside to doing it with locals, unless you consider it confusing code, perhaps it would confuse someone who expects it to be the 'ordinary' Rails way.
I guess someone could argue that Rails templates are inherently tightly coupled to the controller, are meant to be that way, so it's no big deal to use instance variables -- the main negative of which is that it tightly couples your template to a particular controller implementation, but, they'd say, that's fine. I guess that's an opinion, apparently it is Rails' opinion! Certainly plenty of apps are written that way, and it's fine.
But there isn't really anything that's going to get in your way from going against the typical Rails way of doing things, and using local template arguments instead of instance variables. It works fine. I've done it.
I think you are right to be willing to question Rails -- sometimes Rails has made some odd choices. And also right to be cautious about going ahead doing something differently than Rails seems to wants you to, sometimes it does cause problems. In this case, I don't expect it will.

Nope, if you add #some_var = some_value to the index action then it will only be applied to that action. If you want to create a global action then you can apply it to the application.rb.
I highly recommend reading http://guides.rubyonrails.org/action_controller_overview.html
but something that might interest you in specific is this:
4.4 default_url_options You can set global default parameters for URL generation by defining a method called default_url_options in your
controller. Such a method must return a hash with the desired
defaults, whose keys must be symbols:
class ApplicationController < ActionController::Base def
default_url_options
{ locale: I18n.locale }
end end
These options will be used as a starting point when generating URLs,
so it's possible they'll be overridden by the options passed in
url_for calls.
If you define default_url_options in ApplicationController, as in the
example above, it would be used for all URL generation. The method can
also be defined in one specific controller, in which case it only
affects URLs generated there.

Related

In Rails 5, how access cookies method in a model or helper?

How does one access cookies inside a Rails model or helper?
Trying to dry up some controller methods, I am trying to move multiple controller calls to cookies() into application_helper.rb and/or a model.
What doesn't work in application_helper.rb:
cookies[:foo]
ActionDispatch::Cookies.cookies[:foo]
ActionController.cookies[:foo]
ActionDispatch::Cookies::ChainedCookieJars.cookie[:foo]
All of which result in undefined method 'cookies'
Note: Well-meaning answers that simply regurgitate MVC dogma are mis-placed here... I've architected long enough (decades) to know when coloring outside the MVC lines (if possible) is the better route. But the precise syntax eludes me even after digging through Rails source. This question is prompted by a somewhat complex situation related to, among other things, inconsistent browser handling of cookies in cross-domain, ajax environment that sometimes includes local files (for which Chrome declines to manage cookies).
It's not a good idea :) Models are classes, and they shouldn't be aware of what is happening on a web level, and that's why cookies method is implemented in ActionController, but there's no such implementation in ActionModel or ActionHelper. If you need a cookie value in a model, pass a value from a controller then. This is how it should be done.
As #Vasili mentioned, cookies is only available in controllers. But if you want to access cookies in helpers or models, just pass it as an argument, for example:
helper example:
module ApplicationHelper
def some_helper(given_cookies)
given_cookies[:foo] = 'bar'
end
end
In view:
<%= some_helper(cookies) %>

What's a good way to create subjective views in Rails?

I'm wondering what's the Rails Way™ to build a "subjective" view, that is, a view who's output is changed based on a provided param.
E.g., consider the following contrived case:
A user asks for a article in a blog app. In the request, she's sending the param time_zone=(-)1, to indicate that her timezone is -1. The app should then take that param, and in the output return the article and the time of the creation of that article - converted to her timezone.
The rationale for this server-side processing approach, is that it would be easy to change the client view, in particular if one's accessing the rails app through an exposed (mobile) api. (Mind you, it is no problem to handle the before-mentioned case with a client side script etc., but I'd like to avoid that.)
Somehow, I feel that this could easily belong to the model, had it not been for the fact that params isn't available there. I suppose that is for a reason. Controller is, from my point of view, definitely not the place to stick this.
The question is: should I stuff this logic in the view?
This logic would probably fit best in a subclass of your controllers. This subclass could define a filter method that sets appropriate instance variables, which you then reference in your view. I linked to a question that does exactly that for your timezone example (albeit without numeric timezone offsets, but the idea is the same). An alternate example might be say a different UI based on a URL parameter. In that case I might do something like this:
class ApplicationController
before_filter :set_ui
private
def set_ui
if params[:ui] == 'blah'
#ui = 'blah'
else
#ui = 'thing'
end
true
end
end
Then in your view you could have something like this:
<%= render :partial => #ui %>
Then any controllers that you create as subclasses of ApplicationController will have this functionality

Is it poor style to hide instance variables in filters?

I see this all the time in rails code:
before filter :get_post, only: [:edit, :update, :destroy]
def edit
# code .......
end
def update
# code .......
end
def destroy
# code .......
end
private
def get_post
#post = Post.find(params[:id])
end
I understand it keeps from repeating the same line of code three times, but isn't there a much easier to read and better way to accomplish the same thing by just refactoring the code into a private method without hiding the instance variable and the before filter?
private
def get_post(post_id)
Post.find(post_id)
end
Then you can keep the instance variable in the action
def edit
#post = get_post(params[:id])
end
It doesn't make sense conceptually to hide instance variables in private methods. Why is this so prevalent in rails?
You'll probably see a lot of differing opinions on this particular practice. Personally, I agree with you; I feel that the strict adherence to DRY only serves to introduce a bit of confusion by hiding away the instance variable in a method down at the bottom of the file. I'm not even sure a get_post method really buys you very much myself. That said, I tend to prefer explicicity (if that's a word) over terseness in many cases, and for a team that uses this trick consistently in every controller, there may not be as much (if any) confusion.
If the filter encapsulates a little more complexity, it may be worth it. For example, the popular authorization library CanCan by Ryan Bates introduces a controller class macro called load_and_authorize_resource that installes before_filters such that RESTful resources are automatically loaded and authorized for access against the current user. This could potentially be more than a line or two per method, and would not repeated verbatim each time.
There's a popular middle-ground that some Rails devs use, which is to specify the before_filter as a block, so you don't have to repeat yourself, but the instance variable is up at the top of the file:
before_filter(only: [:edit, :update, :destroy]) do
#post = Post.find(params[:id])
end
As mentioned in the comments, there are some gems, like decent_exposure, which help formalize this pattern and make it significantly less "hidden" or confusing (since it's declared explicitly and the API is known).
As other have stated, this is a matter of opinion. I started off using before filters to hide common instance variables between controllers. I still do this on occasion, but I have found it to be a code smell that the controller is bloated. If you have so many instance variables that you need to hide them in filters you are doing too much. My opinion is that the controller action should be very small, and if all the actions have one line in common that handles the instance variable its not an issue.
My remedy for getting my controller actions to a level that made me comfortable with having a common instance variable was to use service object/presenters to consolidate the information that was once in several instance variables into a single instance variable per controller action. I've found this been a huge improvement in the readability and maintainability of my controllers and views.
I think your suggestion is an improvement over the typical Rails convention. I'd take it a step farther, though and drop the instance variable entirely. It's misleading to use an instance variable because it's typically not representing shared state across multiple controller actions, and often not even shared between any methods (except the before filter that assigns it).
The Rails convention of populating an instance variable for use in a view is a bad idea whose use should disappear. In a clean MVC implementation, nothing from the controller would be available in the view. I think this is a much cleaner implementation which properly separates the layers:
def edit
post = get_post(params[:post_id])
render 'edit', locals: { post: post }
end
No leaking state, no reliance on an instance variable in the view, explicit intent, and a view that is more easily reusable and can be rendered from within other views without injecting a hacky inline instance variable assignment.
Rails has a number of conventions that make for quick bootstrapping, but they don't make for good code.
It's a convention over configuration thing.
Neither approach is wrong, but people who like Ruby often prefer minimalist coding patterns, and value using the quickest approach to solve a problem so they can get on to solving more complex and interesting problems
If one has seen this approach used often enough, it becomes second nature to have all of the member functions (obviously not the collection functions) preload the member variable for you before you define your code. It gets to the point where specifying the member variables in the function does not create a significant clarity advantage (if any).
There is also another approach using helper_method. It's looks almost as you want it.
http://rails-bestpractices.com/posts/57-substituting-before_filter-load_object

View helper link_to in Model class

Using Rails 3, is there a way to use link_to helper, or any helper for that matter, inside model?
There are some reasons that you may need link_to in a model. Yes, #andy, it's a violation of MVC, but that doesn't mean you should get points for not answering the question.
#schwabsauce, it's easier than that. The first line isn't even strictly necessary if you do it in an initializer or something. Same thing works for .sanitize and .raw and a whole load of other awesome functions.
ActionView::Base.send(:include, Rails.application.routes.url_helpers)
ActionController::Base.helpers.link_to(whatever)
If you want to use autopaths you may have to do this inside your link_to:
Rails.application.routes.url_helpers.page_path(#page)
Be very careful following the advice outlined in Chuck's post if you're doing this in Rails 3.2.1 . It would seem as though that approach is not a safe way to go about including the link_to helper in non-view classes in Rails 3.2.1. There is a safer way (that works for us in any case) outlined below.
When we used the approach in Chuck's post in one of our classes, it ended up having very troubling and difficult to debug consequences. It ended up causing side effects / bugs that only turned up in very specific (and rare) circumstances.
The problem, as far as we can tell, is that this line:
ActionView::Base.send(:include, Rails.application.routes.url_helpers)
Is telling ActionView::Base to include the Rails.application.routes.url_helpers, which ActionView::Base apparently already does on its own. Having it include the url_helpers a second time, seems to cause re-initialization of the routes state (#_routes in classes that have included the ActionDispatch::Routing::UrlFor module).
This leads to seemingly random and unexplained "undefined method 'url_for' for nil:NilClass" exceptions in views that attempt to call, directly or indirectly, the url_for method after ActionView::Base has included the url_helpers the second time.
The solution that worked for us was instead of telling ActionView::Base to include the url_helpers again, just include the UrlHelper module yourself wherever you might need it.
Then when you need to use link_to and have access to the path you can simply do this (assuming login_path is valid for your app):
include ActionView::Helpers::UrlHelper
...
link = link_to('here', Rails.application.routes.url_helpers.login_path)
It took us a very long time and quite a lot of head scratching to track down the bugs caused by the double-include and I just wanted to warn others to be careful when tweaking the behavior of the Rails base classes.
I got this to work with the following inclusions:
include ActionView::Helpers::UrlHelper
include ActionController::UrlFor
include Rails.application.routes.url_helpers
cattr_accessor :controller
def controller; self.class.controller; end
def request; controller.request; end
Then in my controller I populated the attribute (creating a controller from scratch requires a significant amount of data in the arguments hash).
Lead.controller = self
link_to helper, MVC violation
What Andy said,
if you're generating HTML in the model you probably need to take a big long look at what you're doing and why.
URL helpers
URLs on the other hand often come in handy outside of view-controller code, in all kinds of service/form/api/... classes for example, even in models if you have to.
Yes, Rails.application.routes.url_helpers is a module, but that doesn't mean you should just include it wherever or funny stuff will start happening as Gary put it:
https://www.destroyallsoftware.com/blog/2011/one-base-class-to-rule-them-all
What you can do is:
delegate :url_helpers, :to => 'Rails.application.routes'
and then use, for example
url_helpers.home_url
If you want to use any module stuff anywhere and don't mind going against all Ruby norms, throw this hackery into a service:
module View
extend self
class HelperIncluder
include ActionView::Helpers::UrlHelper
end
def link_to(*args)
HelperIncluder.new.link_to(*args)
end
end
And now:
View.link_to('a', 'b') => "a"
Not without hackery.
If you think you need link_to in a model, you're likely violating some principle of the Model-View-Controller architecture.
A model should be a place for data and business logic, but generating links is almost certainly a job for the controller or view (or, Rails specifically, in a helper class).

Change controller.action_name in Rails

I am in one action on Rails, but I wish to continue processing from within another action. My code looks like this:
send(new_action) #call the new_action method
action_name = new_action #change the controller.action_name
render :action => new_action #inform the view that we're in new_action
this works, but the action_name -- though it changes from within the ActionController instance -- is reset when it's retrieved from within the view as
controller.action_name
is there any way to set it and make it stick?
Edit: I have found another solution which doesn't involve this problem, which is here. Thanks!
You are doing it the wrong way!
That sort of fiddling around in the innards of ActionPack is almost guaranteed to cause you grief sooner or later. There's no guarantee that those internals won't change radically between releases, it's hard to maintain (for yourself and others) as you forget exactly what it's doing and why, and there's the very real possibility of introducing subtle bugs.
Instead, take the code that's common to both actions and put it into a common method, like a private method in the controller, more logic in the models, or, if necessary, a common function higher in the inheritance chain of the controllers.

Resources