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).
Related
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.
I'm a Rails beginner and I learn that I always must try to be more DRY.
I'm have a comment system associated to my content model, and I load my comment with ajax on page scroll.
In my view I have:
%section.article-comments{'data-url' => content_comments_path(#content)}
and in my routes.rb file I have the route
resources :contents, only: :index do
resources :comments, only: :index
end
My comment controller of course is
def index
#content = Content.find(params[:content_id])
#comments = #content.comments
render ...
end
Now I want to add comments also to videos and gallery.
So I need to add a route for every resource and I need a gallery_index and a video_index.
Content, video and gallery index method in comment controlelr are repeated, and I cannot understand how can I be more DRY.
All your controllers presumably inherit from ApplicationController:
class CommentsController < ApplicationController
If you find yourself with a lot of repetition in any of the controller methods you could define it in ApplicationController instead, with maybe some specific processing in each controller.
For example:
class ApplicationController < ActionController::Base
def index
...some common processing...
specific_index_processing
end
private
def specific_index_processing
# empty method; will be overridden by each controller as required
end
end
class CommentsController < ApplicationController
private
def specific_index_processing
...specific procesing for the comments index method...
end
end
And of course, if one of your controllers needs to be completely different from this common approach you can always just override the entire index method.
I hope this is something obvious that I've just consistently overlooked and the community can set me on the right path.
I have a news article controller, but I want to be able to use a "common" ticker list on different views. How do I initialise this "#article_list" if I'm using the partial in a few controllers? Apparently it is of the opinion that using a helper is not the solution, since helpers are just for view logic. So where do I put this initialiser that would be available to every controller as required? I shouldn't put them in application controller should I?
You can use before_filter method, i.e. something like this:
class ApplicationController < ActionController::Base
def set_article_list
#article_list = ArticleList.all # or any onther selection
end
end
class NewsArticleController < ApplicationController
before_filter :set_article_list, only: :action1
def action1
end
end
class AnotherNewsArticleController < ApplicationController
before_filter :set_article_list, only: :another_action1
def another_action1
end
end
UPDATE:
Indeed, there will be problem with a fat ApplicationController. To avoid it it's possible to use module (almost #carolclarinet describe it below):
module ArticleList
def set_article_list
#article_list = ArticleList.all # or any onther selection
end
end
class NewsArticleController < ApplicationController
include ArticleList
before_filter :set_article_list, only: :action1
def action1
end
end
class AnotherNewsArticleController < ApplicationController
include ArticleList
before_filter :set_article_list, only: :another_action1
def another_action1
end
end
And
You can create, essentially, a query object that is only responsible for returning what you need for #article_list, for example, building off of Psylone's answer:
class ArticleList
def ticker_articles
ArticleList.all # or any onther selection
end
end
This class could go in lib, app/models, app/query_objects, app/models/query_objects, wherever it makes sense for you. This is a bit outside The Rails Way so there's no convention about where these types of objects should live.
Then in whatever controller you need this, do:
#article_list = ArticleList.new.ticker_articles
For more explanation of query objects, see this codeclimate article #4. Depending on what you're doing to set #article_list, this might also be called a service object (#2) or something else entirely. No matter what you call it though, its responsibility would be to return the value you need for #article_list and that's it.
I have a question regarding the reuse of code among controller actions. I think it is a fairly standard situation, so I am interested in what's the best practice in Rails.
Let's say I have a films resource with a corresponding FilmsController, which has a nested resource comments served by CommentsController. The nested resource can be rendered on its own using its index and show actions. However, it should also be possible to render the comments embedded in the corresponding film page.
Now, the question goes, what is the best way to reuse the code from CommentsController within FilmsController.show?
1) Force the CommentsController.index to render to a string and then pass it in a variable to the film view?
Or 2) call the CommentsController.index directly in the film view as a kind of "partial", executing the database queries from there?
Or 3) create a separate method in CommentsController responsible for the database handling, call it from both CommentsController.index and FilmsController.show, and use the corresponding view in both the places, too?
To me the options 1) and 2) seem a bit messy, while 3) is not modular and involves some repeating of code. Is there any better way to accomplish this?
Thanks a lot!
Now, the question goes, what is the best way to reuse the code from CommentsController within FilmsController.show?
You could move the shared controller logic into a inside your application controller (or a lib and require it appropriately), a la:
class ApplicationController < ActionController::Base
def foo
#foo = "foo"
end
end
Comments Controller:
class CommentsController < ApplicationController
before_filter :foo, :only => [:index]
def index
end
end
Films Controller:
class FilmsController < ApplicationController
before_filter :foo, :only => [:show]
def show
end
end
For repeated view logic you can move that to a common folder, say your_app/app/views/shared/_foo.html.erb and render that appropriately.
Another option is to place the relevant code into an external module:
lib/mymodule.rb
module MyModule
def foo
end
end
And then you can include the module inside your controller or anywhere you want access to your foo method.
class CommentsController < ApplicationController
include MyModule
def index
foo()
end
end
I have my regular ApplicationController class & I have a Admin::ApplicationController class. The problem is that Admin::ApplicationController doesn't seem to be getting loaded or executed or anything. Am I not allowed to have a namespaced application controller? The reasoning for wanting to have it is so that I can check if a user is an admin w/ CanCan & redirect them out if they're not.
Call this controller Admin::BaseController, as it is more to act as the base of the namespace than to do anything for the appilcation. For it to do what you want to do, you will need to make all admin namespaced controllers inherit from this controller.
The only times I've seen namespacing like that is when the controller is nested in a subfolder. So Admin::ApplicationController would expect to be in controllers/admin/application_controller.rb
One possible solution:
If you want everything except your home page to kick them out, simply set a before_filter on your application controller with an exception for home/index like this:
ApplicationController.rb
before_filter :authorize_admin
def authorize_admin
//dostuff
end
HomeController.rb
skip_before_filter :authorize_admin, :only => ['index']
Where index is your action that you want to skip. Leave off the only to skip the filter for the whole controller.