Is the controller before_action :set_x method needed? - ruby-on-rails

If I use the Rails scaffold generator for the Article resource it will create a before filter and a private method like this
# app/controllers/articles_controller.rb
before_action :set_article, only: [:show, :edit, :update, :destroy]
...
private
def set_article
#article = Article.find(params[:id])
end
Now I understand this makes your controller DRY because instead of adding #article = Article.find(params[:id]) 4 times which is 4 lines of code, you can use the above and you save yourself... well it's still 4 lines and a similar number of keystrokes. But if you want to modify it you only have to do it in one place. But you sacrifice clarity because to see that #article is defined in those 4 actions you have to look at the before filter at the top, then at the private method at the bottom, then back at the action. For me personally this is no benefit so I always just put this in the four actions. My question is, is this merely a personal preference issue or is there a compelling reason to always use the DRY method that I am not seeing?

When the logic of retrieving gets a bit more complicated than just one line it is quite helpful, but not by any means necessary. You could also leave the set_article method as it is, remove the before_action and call it explicitly in the actions that need it, a bit of a trade off between before_action and repeating it in all actions. That's quite handy when the logic is a few lines of code instead of just one.

Related

Share Controller Actions / Objects on Rails

As a Rails Rookie I wonder what is the proper way to reuse code that interact with database objects. For instance I have:
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
def index
#posts = Post.all
end
end
I also have a Welcome page with a Welcome controller:
class WelcomeController < ApplicationController
def index
#posts = Post.all
end
end
I would like to ALSO show the same list of posts on the Welcome page. I implemented a partial view for Posts and used it in Welcome view, but repeating the same database query on Welcome controller seems to be the wrong way to do it since I am repeating myself.
I also called the other controller action like this but I don't know if this is acceptable.
class WelcomeController < ApplicationController
def index
#posts = PostsController.new.index
end
end
I wonder if there is a right way (rails way) to share actions between controllers. In essence from WelcomeController call index action of PostsController so I don't have to implement repeat the method
Good question!
It might seem like too much repetition, but it is totally fine to repeat the database query in the controllers. Don't instantiate a new controller instance and then call the index method on it like you did.
Remember that the HTTP requests get routed to the right controller action and then render a view. So refactoring same views into partials and rendering those is perfectly fine, but don't bother about repeated code in the controllers, especially if it is just a simple query.
Once your app will grow, you will have more code in the controllers and it won't look like repetition so much.
If you have larger code blocks that are repeated, you can work with concerns (How to use concerns in Rails 4 they work for models, too).

How to do multiple before_filters on certain Rails Action

I looked at the following questions:
Are multiple before_action calls bad code style?
Api Dock - before_filter
And I think there was something about creating a controller superclass, but I am not familiar with such.
The reason I need to do this is because, we have a bunch of setup views, and those views are renering a seperate layout. setup_screen
So instead of doing
def setup_step_1
render: layout => 'setup_screen'
end
def setup_step_2
render: layout => 'setup_screen'
end
def setup_step_3
render: layout => 'setup_screen'
end
We created the following:
before_action :setup_layout, only: %i[ setup_step_1 setup_step_2
setup_step_3]
The problem is, we want to run some extra logic in the setup_screen's. But we can't add another before_filter. It's also not possible to add the logic in the def setup_screen because of the case that not ALL setup_screen's will need it.
As stated in those answers, there is no objection to using multiple before-filters. So you can easily add multiple before_action. E.g. a typical example
before_action :authenticate_user!
before_action :get_post, only: [:show, :edit, ...]
In your case for the render I would use a after_action, since the render is the last action. Then you could add your conditionial code in each separate action, or an extra before_action.
An alternative method, which I would prefer in your case is something like the following:
def setup_step_1
setup_step(1)
end
def setup_step_2
setup_step(2)
end
def setup_step_3
setup_step(3)
end
protected
def setup_step(step)
if step == 1
# .. do something for step 1
elsif step == 2
else
end
render: layout => 'setup_screen'
end
I would prefer this approach since it is a little more expressive/explicit. Looking at the methods it is clear they share the same core. Personally I prefer to use before_action for setting up pre-conditions: e.g. authentication, authorization, fetching the data if very simple.
But as usual in programming, there are a lot of roads leading to a working application and sometimes it is just a matter of taste which you prefer.

Using Rails 5, how do I DRY'ly define instance variables in multiple controllers/actions?

I need to define the same instance variables:
#note = Note.new
#notes = current_user.notes.all
In multiple controllers:
UsersController#home
NotesController#create
NotesController#update
Where would be an appropriate place to house a class/function that
def createNoteInstancesVars
#note = Note.new
#notes = current_user.notes.all
end
?
Is this the intended use of a controller concern or is there a different/better way I am not thinking of? If I do put it in a concern, doesn't that mean these queries are getting run for every single controller#action? I would like to avoid that.
You could add the method in your ApplicationController.
Then add in NotesController:
before_action :create_note_instances_vars, only: [:create, :update]
Same logic in UsersController
I think concern is not good idea for just initialise instance variable.
You can write this def in ApplicationController and call via callbacks when you need it in any controller.

New page not loading - ActiveRecord::RecordNotFound

This strikes me as pretty simple, which is why it's bugging me that I can't figure it out. I am creating a basic news app in rails. I have a "Posts" controller that controls the content for my basic pages. I want to have a page with local news. I went into the posts controller and made a new action called "local"
def local
end
I then went into my routes.rb file and made a route for the page.
get "posts/local" => "posts#local"
I then created a local.html.erb file and placed it in my post views.
When I try to click a link with the posts_local_path, I get this:
ActiveRecord::RecordNotFound
Extracted source (around line #74):
72
73
74
75
76
77
# Use callbacks to share common setup or constraints between actions.
def set_post
#post = Post.friendly.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
I don't understand. This is not involved in any way with the page I am trying to open. I'm not using that part of the controller. I'm stuck. Can someone help me understand what I'm doing wrong? Thank you!
I'm assuming you have before_action in your controller
You want to ensure that's not called for the local action
before_action :set_post, except: [:local]
or if it's only used in show
before_action :set_post, only: [:show]
You can pass either except or only arrays of the action names as symbols.
The problem originates from the fact that you're not passing any id in the params to the controller. Hence your method set_post cannot retrieve any post and returns the ActiveRecord error.
So you've got 2 options:
Either you want to use set_post and need to specify an :id param in your routes
Or you avoid calling set_post by adapting your before_action filter (as per the answer of j-dexx)

What order do before filters occur in?

What order do before filters occur in? Specifically, what order do the before_action filters occur in, in regards to inheiritance? For example, will this work:
class A < ActionController::Base
before_action :set_user
def set_user
#user = something
end
end
class B < A
before_action :set_post
def show
render #post
end
def set_post
#post = #user.posts.first
end
end
Will B#show work? What are the rules for filter order for future reference? I can't find any of this in the Rails documentation.
I suggest taking a look at the source code and API Docs on filters.
The default ordering should be
:set_post
:set_user
I think if you wanted to push :set_user to the top of the stack you could change the line in A to
prepend_before_action :set_user
Also worth pointing out, this isn't the only question on the topic; there are others here on SO.
As for your specific situation, it looks like you'll need to change A as I mentioned above in order to have #user be assigned by the time set_post in B runs.
As of 4.2.6 (probably changed in an earlier version), the ordering is now parent before child:
:set_user
:set_post
The accepted answer is no longer true as of Rails 4.2.6. It was probably changed earlier, but that's the version I'm on right now.
Parent filters are now called before child filters, so the ordering will now be:
:set_user
:set_post
And, if you think about it, that ordering really makes more sense. As previously said, if you need the child to call the filter before the parent, you need to use prepend_before_action.
Yes, your code will work as intended.
The before_filters on the derived class (B) are called before the before_filters on the base class (A). So the order of the methods called are:
set_user()
set_post()
show()
Also note, after_filters are called in reverse order, the most derived first.

Resources