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?()
Related
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)
I am trying to access helper method in my controller using helpers like below:
class MyController < ApplicationController
def index
#foo = 'bar'
helpers.my_helper_method
end
end
Inside Helper method, I am trying to access an instance variable of controller
module MyHelper
def my_helper_method
#some manipulation on foo
#foo.to_i
end
end
But in above scenario #foo is nil. When I call the same method from view, #foo is available. So the instance variable can be passed to helper method only through UI or some other way is there?
Thanks in advance.
UPDATE:
view_context
seems like reasonable solution https://apidock.com/rails/AbstractController/Rendering/view_context
class MyController < ApplicationController
def index
#foo = 'bar'
helpers.my_helper_method(#foo)
end
end
module MyHelper
def my_helper_method(foo)
#some manipulation on foo
foo.to_i
end
end
pass it as argument.
You can access instance variables that you set in a controller in your helpers. If the value is nil, then you need to deal with it in your helper:
module SomeHelper
def do_something
return 0 if !#value
value * 3
end
end
class SomeController
def index
#value = 1
helpers.do_something
end
def show
#value = nil
helpers.do_something
end
end
I have many controllers where I set variables so that the appropriate views gain visibility to these variables. For example,
class UsersController < ApplicationController
...
def index
#users = User.all
end
...
This works. So how come this doesn't work:
class PlacesController < ApplicationController
...
def show
#params = params
end
...
If I byebug in the show-method, I can access params. If I byebug in the view (places/show.html.erb), then "params" and "#params" return nil.
Does this have something to do with the fact that "User" is an ActiveRecord and "Place" is not? How can I make arbitrary data accessible to the view?
You can use your controller instance variable #params in your /places views. But because you did not pass any params, params returns nil.
So, if this variables was out visibility, you would get NameError.
Try to set #params directly like #params = { foo: "bar" } and you will see it.
More about params in Rails here (#4 Parameters).
At this time I need to use three variables in three different methods with the respective file in the view, but I don't want to define each variable in each method.
How can I define the variable just once and have access to the variables from all the three methods?
Those are the methods:
def pendientes
end
def proceso
end
def finalizadas
end
Those are the three variables:
#pendientes = Support.where("estado = ?", 1)
#procesos = Support.where("estado = ?", 2)
#finalizadas = Support.where("estado = ?", 3)
How do I need to define the variables to do it?
If you're working in the context of a controller, you could do something like this:
class MyController < ApplicationController
before_filter :initialize_variables
def pendientes
end
def proceso
end
def finalizadas
end
private
def initialize_variables
#pendientes = Support.where(estado: 1)
#procesos = Support.where(estado: 2)
#finalizadas = Support.where(estado: 3)
end
end
Update:
If you roll with a before_filter, it may be a good idea to add only: [:pendientes, :proceso, :finalizadas] so that future actions added to the controller don't initialize the three variables.
Another option would be to delete the before_filter call and just invoke the initialize_variables method from each of the three existing actions.
in my project.rb model, I'm trying to create a scope with a dynamic variable:
scope :instanceprojects, lambda {
where("projects.instance_id = ?", current_user.instance_id)
}
I get the following error:
undefined local variable or method `current_user' for #<Class:0x102fe3af0>
Where in the controller I can access current_user.instance_id... Is there a reason the model can't access it and a way to get access? Also, is this the right place to create a scope like the above, or does that belong in the controller?
This doesn't make much sense, as you already pointed. The current_user doesn't belong to model logic at all, it should be handled on the controller level.
But you can still create scope like that, just pass the parameter to it from the controller:
scope :instanceprojects, lambda { |user|
where("projects.instance_id = ?", user.instance_id)
}
Now you can call it in the controller:
Model.instanceprojects(current_user)
The already accepted answer provides a really correct way to achieve this.
But here's the thread-safe version of User.current_user trick.
class User
class << self
def current_user=(user)
Thread.current[:current_user] = user
end
def current_user
Thread.current[:current_user]
end
end
end
class ApplicationController
before_filter :set_current_user
def set_current_user
User.current_user = current_user
end
end
This works as expected, however it can be considered dirty, because we basically define a global variable here.
Ryan Bates lays out a pretty safe way to implement this kind of strategy in this railscast
You can browse the source code here
Here he creates a current_tenant method, but you could easily substitute current_user instead.
Here are the key bits of code...
#application_controller.rb
around_filter :scope_current_tenant
private
def current_tenant
Tenant.find_by_subdomain! request.subdomain
end
helper_method :current_tenant
def scope_current_tenant
Tenant.current_id = current_tenant.id
yield
ensure
Tenant.current_id = nil
end
#models/tenant.rb
def self.current_id=(id)
Thread.current[:tenant_id] = id
end
def self.current_id
Thread.current[:tenant_id]
end
Then in the model you can do something like...
default_scope { where(tenant_id: Tenant.current_id) }
You don't need to use scopes. If you have set the appropriate associations in models, following piece of code placed in controller should do the trick:
#projects = current_user.instance.projects