Change controller.action_name in Rails - ruby-on-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.

Related

ERB scope: instance variable vs locals

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.

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

Caching a method that's got conditional output

I have a method with is used to render news articles on every page. If you aren't on a care home page then it won't render specific news to that home. It's quite a heavy thing to process on every single page request. I was wondering if anyone could come up with a good way of caching it.
def articles_to_show
#articles = #care_home ? #care_home.news_items.latest.translated.limit(4).includes(:images) : Refinery::News::Item.latest.hidden_from_main.translated.limit(4).includes(:images)
end
Here's the view template - http://pastebin.com/BAmgSZia
I've tried fragment caching it, but then I realised every now and then if it expires and the first request is that of a care home, then the news articles will be populated incorrectly.
You have ugly code already so it wouldn't be very bad to make this code a little more obfuscated ...
#articles = lambda { YOUR_HEAVY_CODE }
Then in your view you're:
- cache do
- #articles = #articles.call
= #rest of the partial
Thing worth mentioning is that, if you can't use any embedded and suggested way of caching, it's probably something wrong with your design.
Relating your action instance variable with other instance variable probably initialized in some filter is bad design example.
#articles_to_show it's bad example of REST approach, you can instead do something like:
class Articles::CollectionController with show method
Don't use ternarny operator, probably at all, it's so obfuscated. ?: syntax is proper only for very easy statements.
Don't place so much to the controller, try to delegate your methods, scopes by merging them into another method - it would be cleaner

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

Conditional in Rails partial depending on the context page?

I was wondering if I could execute / display some stuff differently in partials depending on the context in which it appears.
For example, I have a _user_info partial that appears in a sidebar, and also in the user page, and I want to display some extra info in the second case. How can I express that kind of conditions?
You can use controller_name and action_name methods.
if controller_name == 'user' && action_name == 'show'
details
end
What you're asking for is something this:
if params[:controller].eql?('users')
view code
However I would split the partial into two separate partials and display the distinct parts from whichever view you need.
Sure, it just depends on how you want to discover/expose that context. The simple way is conditionals, which can be set in any number of ways, or derived from a set of conditions. That can happen in a filter, an action, etc.
If the changes are large enough, better to just encapsulate them in separate partials.
Another option is to wrap the render tag in a helper that calculates/grabs those conditions.
It kind of depends on the nature of the conditions, where/how you want to deal with them, etc.
Or pass a variable into the partial via :locals =>

Resources