In my project I am declaring instance variable with the help of before_action callback, but some of my controllers have same callback code. ex:
my golf_courses_users_controller.rb looks like:
private
def require_user_club_and_golf_course
#club_admin_user = User.find(params[:user_id])
#club = Club.find(params[:club_id])
#golf_course = GolfCourse.find(params[:golf_course_id])
end
my course_holes_controller.rb looks like:
private
def initialize_objects
#user = User.find(params[:user_id])
#club = Club.find(params[:club_id])
#golf_course = GolfCourse.find(params[:golf_course_id])
end
An easy way to set this up is to have the method assigning the instance variables in a parent class (for the sake of simplicity, I'll use ApplicationController here), and then call the before_action in the relevant controllers.
application_controller.rb
def initialize_objects
#user = User.find(params[:user_id])
#club = Club.find(params[:club_id])
#golf_course = GolfCourse.find(params[:golf_course_id])
end
golf_courses_users_controller.rb
before_action :initialize_objects
course_holes_controller.rb
before_action :initialize_objects
Depending on how widespread the use of this before action will be, you could even move the before_action to the same parent controller, and skip this where not needed:
application_controller.rb
before_action :initialize_objects
a_controller_not_using_the_before_action.rb
skip_before_action :initialize_objects
That seems dry, clean and pretty conventional to me - let me know what you think and if you have any questions.
I would produce a helper and use it everywhere:
module DbHelper do
def self.user_club_course(params)
# maybe raise unless
# params.values_at(*%i[user_id club_id golf_course_id]).none?(&:nil?)
[
User.find(params[:user_id]),
Club.find(params[:club_id]),
GolfCourse.find(params[:golf_course_id])
]
end
end
And use it like:
#user, #club, #golf_course = DbHelper.user_club_course(params)
Related
I'm new to Rails, and I was thinking that a method should be defined for every single route.
However, /hello_world works as long as I write as below:
Rails.application.routes.draw do
get "/hello_world", to: "hello#world"
end
class HelloController < ApplicationController
# no world
end
# app/views/hello/world.html.erb
hello world!
Is it an expected behavior? If so, is it common not to write it?
You only need a controller action if you have any processing / data retrieval that you need to do before you display the view.
So, yes, the method itself isn't necessarily needed.
You might need to retrieve a record in a show action like this...
class CustomersController < ApplicationController
def show
#customer = Customer.find(params[:id])
end
end
but inn some cases, you might have a before_action that does whatever's neeeded for several methods, so you (again) don't need to specify the action method.
class CustomersController < ApplicationController
before_action :set_customer only: [:show, :edit, :update]
private
def set_customer
#customer = Customer.find(params[:id])
end
end
So this is a case where you might have needed to define a method for the action, but you've now made it unneccessary.
I have a question concerning best practices in rails.
In my rails project I have the following code:
class MyController < ApplicationController
def some_method
#product = MyFabricatorClass.new.create_product
end
...
end
MyFabricatorClass is not dependent of some state and its behaviour is constant. I am also doing a lot of C++ stuff and to me it feels kind of unefficient to always instantiate a new MyFabricatorClass object. In a C++ project I would propably use something like:
class MyController < ApplicationController
##my_fabricator = nil
def some_method
##my_fabricator ||= MyFabricatorClass.new
#product = ##my_fabricator.create_product
end
...
end
Is this style also legit in Rails? What would be the typical rails way to do it?
Thanks for any advice...!
It is a better practice to not use class variables (those that start with ##) in ruby; see here why
This might look like a weird code, but this is the more conventional way:
You set a "class" instance variable, instead of setting a "class variable".
class MyController < ApplicationController
#my_fabricator = nil
class << self
def some_method
#my_fabricator ||= MyFabricatorClass.new
#product = #my_fabricator.create_product
end
end
end
About class << self, see here
The above code is just the same as:
class MyController < ApplicationController
#my_fabricator = nil
def self.some_method
#my_fabricator ||= MyFabricatorClass.new
#product = #my_fabricator.create_product
end
end
Now you can just do:
MyController.some_method
I want to assign the same set of instance variables for multiple emails based on the argument passed into mailer. Something like this:
class UserMailer < ActionMailer::Base
before_action -> { set_defaults(order) }, only: [:email1, :email2]
def email1(order)
...
end
def email2(order)
...
end
private
set_defaults(order)
#order = order
#customer = order.customer
#price = order.price
end
end
I see that you can pass params, strings, and the like to before_actions in controllers: Rails 4 before_action, pass parameters to invoked method
And it looks like I can use process_action as workaround: Rails before_action for ActionMailer that would use mailer arguments
But is there a way to access arguments? Are they out of scope? Help.
This ?
DO_ME_BEFORE = [:email1, :email2]
def process_action(*args)
return super unless DO_ME_BEFORE.include?(args[0].to_sym)
#order = args[1]
#customer = #order.customer
#price = #order.price
super
end
Edit:
I don't think you can hook exactly in the same way as with before_action, but you can simulate it, since your args[0] will be the name of the method. If you write the array of allowed methods as strings, you don't need to call .to_sym on args[0] on the .include?()
I have a project that's set up with the following models. Each -> represents a has_many relation:
Users->Goals->Milestones
My routes for the Milestones look like this:
user_goal_milestones GET /users/:user_id/goals/:goal_id/milestones(.:format) milestones#index
POST /users/:user_id/goals/:goal_id/milestones(.:format) milestones#create
new_user_goal_milestone GET /users/:user_id/goals/:goal_id/milestones/new(.:format) milestones#new
edit_user_goal_milestone GET /users/:user_id/goals/:goal_id/milestones/:id/edit(.:format) milestones#edit
user_goal_milestone GET /users/:user_id/goals/:goal_id/milestones/:id(.:format) milestones#show
PUT /users/:user_id/goals/:goal_id/milestones/:id(.:format) milestones#update
DELETE /users/:user_id/goals/:goal_id/milestones/:id(.:format) milestones#destroy
I find myself in many of the "functions" in the Milestones controller doing a lot of this:
def index do
#user = User.find(params[:user_id])
#goal = Goal.find(params[:goal_id])
end
def edit do
#user = User.find(params[:user_id])
#goal = Goal.find(params[:goal_id])
end
How can I modify my controller so I don't have to define #user and #goal all the time?
I tried putting them directly at the top, right after the start of the class definition block, but it didn't work.
If the params are always the same you can create a method like this
def set_user_and_goal
#user = User.find(params[:user_id])
#goal = Goal.find(params[:goal_id])
end
and put it in a before_filter at the top
before_filter :set_user_and_goal
and set it to whatever action you like
before_filter :set_user_and_goal, :only => [:edit, :index]
Edit:
Also, to make sure that it doesn't blow up in your face, you can do
#user = params.has_key?(:user_id) ? User.find(params[:user_id]) : nil
and as requested.. make sure that the goal belongs to the user by doing something like
#goals = #user.goals.find(params[:goal_id])
you an always define your own helper methods
def goal_milestone(goal)
user_goal_milestone(goal.user, goal)
end
You can add it to your application_helper, and then use any in any of your views. This would create the small helper methods as you asked in your question.
looking for a gem that does this for you didn't show me anything, but you can code this yourself in a generic way.
Suppose I have some logic in a base controller to pass information to the view to build something like a breadcrumb:
class ContextAwareController < ApplicationController
after_filter :build_breadcrumb
def build_breadcumb
#...
end
end
I want this build_breadcrumb method to run after the main controller logic, but before the view is rendered.
The above code runs too late, but a before_filter would be too early.
Can anybody suggest a way to accomplish this without explicitly calling build_breadcumb at the end of each of the actions in the child controllers?
Thanks
I had the same problem and solved it like this:
class ApplicationController < ActionController::Base
def render *args
add_breadcrumbs
super
end
end
There are also some gems to achieve this. One of them is rails3_before_render.
It works similarly to filters, for example:
class PostsController < ApplicationController
before_render :ping, :except => [:destroy]
def index; end
def new; end
def show; end
def destroy; end
private
def ping
Rails.logger.info "Ping-Pong actions"
end
end
(code snipped copied from gem documentation)
I believe rendering starts when render is called, and there's no default way to defer it. Here's one thing you could do:
filters are applied in the same order declared. So make a second after-filter that calls render with an array args stored in a class variable. Then anywhere you would normally call render, set the variable.
If we're overriding render, we're not really using the filter chain at all, so it might be simpler to determine which action we're in using the #_action_name.
StuffController < ApplicationController
def my_filter
# Do the stuff
end
def render(*args)
my_filter if #_action_name == "show"
super
end
end
You can use alias_method_chain like this
alias_method_chain :render, :before_render_action
this will create 2 methods :render_with_before_render_action and :render_without_before_render_action. If you call render, then :render_with_before_render_action will be called. You can override this method
def render_with_before_render_action(*options, &block)
<your code>
render_without_before_render_action(*options, &block)
end
If you don't want your code to be executed and you want to have default render then you should directly call the :render_without_before_render_action
You can do like this to fake a before_render:
class StuffController < ApplicationController
before_filter :my_filter, only: [:index, :show]
def my_filter
#my_filter = true
end
def _my_filter
# Do the actual stuff here
end
def render(*args)
_my_filter if #my_filter
super
end
end
Thanks to #joshua-muheim for the tip about using render