is there any way to persist (preserve) parameters in Rails controller? It should be passed to every action, then to every view and every link.
Example situation:
I have entity A with its controller. Besides, I have another entity B which is dependent on A. I need to access the "parent" A entity very often, so I'd like to have it still as
http://some_url/b_controller/b_action?a_entity=xyz
You should be able to do everything from your controller, using a combination of before_filter and default_url_options :
class MyController < ApplicationController
before_filter :set_a_entity
def set_a_entity
#a_entity = params['a_entity']
# or #a_entity = Entity.find(params['a_entity'])
end
# Rails 3
def url_options
{:a_entity => #a_entity}.merge(super)
end
# Rails 2
def default_url_options
{:a_entity => #entity}
end
end
This doesn't solve the problem of setting the initial value of #a_entity, but this can be done from anywhere (view, controller, etc).
If you want this parameter passed around in multiple controllers, you can replace MyController < ApplicationController with ApplicationController < ActionController::Base and it should work as well.
Hope this helps.
why not put it in a session parameter then?
session["a_entity"] = "xyz"
that way you can access it in all your other controllers too until you clear it or it expires.
more info here:
http://api.rubyonrails.org/classes/ActionController/Base.html
Related
Is there any way to disable the automatic passing of Rails instance variables to the views when they render? I'd like to be able to turn this off and then see where things fail in order to target refactoring.
You should be able to do that by overriding view_assigns in your controller:
class SomeController < ApplicationController
protected
def view_assigns
{} #an empty hash
end
end
I added #sort_by attribute to my controller, and initialized it's value like this:
class ProductsController < ApplicationController
def initialize
#sort_by = :shop_brand
end
...
end
This caused the default application layout not to be used.
Why ?
What is the right way to add an attribute to a controller and initialize it ?
Overriding the constructor is probably a bad idea (as you have found). You should use a before_filter:
class ProductsController < ApplicationController
before_filter :set_defaults
...
private
def set_defaults
#sort_by = :shop_brand
end
end
However, it sounds like you want to keep state. The easiest is to store in the user's session which will automatically persist per user until they close the browser:
def set_defaults
session[:sort_by] ||= :shop_brand
end
The other option would be to pass the current sort_by value in the URL. This is harder to implement though as you'll need to ensure each link or form copies the value over to the next request. The advantage of this however is the user could have multiple tabs open with different orderings and any bookmarked link would restore the same ordering next time. This is the approach that things like search engines would use.
I am trying to access an instance variable which is set in the controller in the model. The controller is the products controller and the model is the products model. The instance variable is a instance of another model called account.
The instance variable is #current_account
When I run the code nothing happens, I do not get an error. Does anyone know where I can find something read about access instance variables set in the controller from the model?
Thanks
Eef
You shouldn't generally try to access the controller from the model for high-minded issues I won't go into.
I solved a similar problem like so:
class Account < ActiveRecord::Base
cattr_accessor :current
end
class ApplicationController < ActionController::Base
before_filter :set_current_account
def set_current_account
# set #current_account from session data here
Account.current = #current_account
end
end
Then just access the current account with Account.current
DISCLAIMER: The following code breaks MVC conventions, that said...
Using class attributes can probably lead to thread safety issues. I would use Thread.current + around_filter to store controller related data at thread level, and ensure it gets cleared
just before the request finishes:
class ApplicationController < ActionController::Base
around_filter :wrap_with_hack
def wrap_with_hack
# We could do this (greener solution):
# http://coderrr.wordpress.com/2008/04/10/lets-stop-polluting-the-threadcurrent-hash/
# ... but for simplicity sake:
Thread.current[:controller] = self
begin
yield
ensure
# Prevent cross request access if thread is reused later
Thread.current[:controller] = nil
end
end
end
Now the current controller instance will be avaliable globaly during the request processing through Thread.current[:controller]
If you need to access a controller variable from a model it generally means your design is wrong because a controller serves as bridge between view and model (at least in Rails), controller gets info from models, models shouldn't know anything about controllers, but if you want to do it anyway you can do it just as jeem said, but I'd rather do:
class << self
attr_accessor :current
end
instead of cattr_accessor :current
you can see why here => cattr_accessor doesn't work as it should
I can't comment directly so I'll post here: the accepted answer does not seem to be right. As #vise notes, class variables are shared across requests. So unless there's just one current account for the entire app, this won't behave as expected.
For more, see the accepted answer by #molf here: Is Rails shared-nothing or can separate requests access the same runtime variables?
I'm not sure if I understand the question exactly, but I'll take a stab.
I think if you need to access a controller instance variable from the model then you either need to make it an attribute in the model, or move your logic to the other class controller, not model.
I am in the middle of migrating my application from using subdirectories for userspace to subdomains (ie. domain.com/~user to user.domain.com). I've got a method in my user class currently to get the "home" URL for each user:
class User
def home_url
"~#{self.username}"
# How I'd like to do it for subdomains:
#"http://#{self.username}.#{SubdomainFu.host_without_subdomain(request.host)}"
end
end
I'd like to update this for subdomains, but without hardcoding the domain into the method. As you can see, I am using the subdomain-fu plugin, which provides some methods that I could use to do this, except that they need access to request, which is not available to the model.
I know it's considered bad form to make request available in a model, so I'd like to avoid doing that, but I'm not sure if there's a good way to do this. I could pass the domain along every time the model is initialized, I guess, but I don't think this is a good solution, because I'd have to remember to do so every time a class is initialized, which happens often.
The model shouldn't know about the request, you're right. I would do something like this:
# app/models/user.rb
class User
def home_url(domain)
"http://#{username}.#{domain}"
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
def domain
SubdomainFu.host_without_subdomain(request.host)
end
# Make domain available to all views too
helper_method :domain
end
# where you need it (controller or view)
user.home_url(domain)
If there is such a thing as a canonical user home URL, I would make a configurable default domain (e.g. YourApp.domain) that you can use if you call User#home_url without arguments. This allows you to construct a home URL in places where, conceptually, the "current domain" does not exist.
While molf's answer is good, it did not solve my specific problem as there were some instances where other models needed to call User#home_url, and so there would be a lot of methods I'd have to update in order to pass along the domain.
Instead, I took inspiration from his last paragraph and added a base_domain variable to my app's config class, which is the set in a before_filter in ApplicationController:
module App
class << self
attr_accessor :base_domain
end
end
class ApplicationController < ActionController::Base
before_filter :set_base_domain
def set_base_domain
App.base_domain = SubdomainFu.host_without_subdomain(request.host)
end
end
And thus, when I need to get the domain in a model, I can just use App.base_domain.
I would like to add a couple of instance variables to my controller, since the variables in question are required from within more than one action's view. However, the below example does not work as I would expect.
class ExampleController < ApplicationController
#var1 = "Cheese"
#var2 = "Tomato"
def show_pizza_topping
# What I want is the above instance vars from within the view here
end
def show_sandwich_filling
# What I want is the above instance vars from within the view here
end
end
As I understand it, Rails takes the instance variables from the controller and makes them available in the view. If I assign the same variables within the action methods, it works fine - but I don't want to do it twice. Why does my way not work?
(Note: this is a bit of a rubbish example, but I hope it makes sense)
EDIT: I have found the answer to this question here: When do Ruby instance variables get set?
EDIT 2: when is the best time to use filters such as before_filter and the initialize method?
These types of things should be handled in a before_filter. A before filter, like the name implies, is a method that will get called before any actions, or only the ones you declare. An example:
class ExampleController < ApplicationController
before_filter :set_toppings
def show_pizza_topping
# What I want is the above instance vars from within the view here
end
def show_sandwich_filling
# What I want is the above instance vars from within the view here
end
protected
def set_toppings
#var1 = "Cheese"
#var2 = "Tomato"
end
end
Or, you could have your before_filter only work on one of your actions
before_filter :set_toppings, :only => [ :show_pizza_topping ]
Hope this helps.
EDIT: Here's some more information on filters in ActionController.
Those aren't instance variables, are they?
class A
#x = 5
def f
puts #x
end
end
A.new.f
=> nil
You're defining it at the class-level, not the instance-level. As "theIV" points out, you need to assign them inside an instance method.