Ruby on Rails - Access controller variable from model - ruby-on-rails

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.

Related

Rails convention for method in multiple controllers

I have an app that has users whose profiles are accessible via site.com/username. When choosing a username, I make an AJAX call to a method in my UsersController to make sure the username is available (and check on the back end as well when submitted). I now want to add groups that will also be accessible through site.com/groupname. Since group and user names cannot collide, whatever controller method that responds to the AJAX call will need to check both so the check_username_available and check_groupname_available methods will do the exact same thing. What's the best practice / Rails way to handle this since I don't want to replicate code in both UsersController and GroupsController?
Having a method for each controller seems a bit redundant, even if the functionality is pulled out to a helper, since there will still be two routes that do the same thing. Having a separate controller solves the problem too but not sure this is good Rails practice.
code that is reused can be shared via a module
class UsersController < ActionController::Base
include NameUniqueness
end
class GroupsController < ActionController::Base
include NameUniqueness
end
module NameUniqueness
protected
def check_name
# implementation here
end
end
both controllers will now have access the check_name instance method.
DanPickett's answer is great.
Another choice is to make a class method in the user model and just call it from each controller. Since this name checking seems like a job for the model, that's what I would do.
class User
def self.check(stuff) ...

Call a controller's method in other controllers (staying DRY)

I'm slightly new to Rails (i.e. stupid and need some teachin').
I have a controller (call it ControllerFoo) that performs a particular task (theMethod) which could be useful in other controllers (say, from within ControllerBar). So, of course, the method is defined as self.theMethod in ControllerFoo (which means it's a class method, right?), and access in ControllerBar as ControllerFoo.theMethod. Confused yet?
Here's the problem: the ControllerFoo.theMethod uses session data, and when called from ControllerBar, session is nil. In fact, it seems that session is also nil when being called from itself. I guess what I'm saying is class methods can't access session data?
<rant>I hate how session data can't simply be accessed anywhere like in PHP</rant>
So for now, since I'm not smart enough to know how to do this correctly, I've just duplicated the logic in several places throughout my app. But this is not DRY at all, and I hate it.
So how can I create a method in a controller that's accessible to other controllers and can also access session data?
class ControllerFoo < ApplicationController
def self.theMethod (greeting)
p "#{greeting} #{session[:user]}!"
end
end
class ControllerBar < ApplicationController
def show
ControllerFoo.theMethod("Hello,")
end
end
Couple of options...
Put the shared method in the shared parent ApplicationController
Create a module that both ControllerFoo and ControllerBar will include
e.g.
module SharedModule
def theMethod (greeting)
p "#{greeting} #{session[:user]}!"
end
end
class ControllerFoo < ApplicationController
include SharedModule
end
class ControllerBar < ApplicationController
include SharedModule
def show
theMethod("Hello,")
end
end
The way you would do this is Ruby would be to create a module containing the class (or instance) methods you wish to share and include it in the classes you need to have those methods defined in.

how to pass session variable to model in RoR?

I used a global variable in my app for passing information before. But I got a problem and thanks everyone here suggested me to store those data in session with database.
I tried, but I found that I can't access the session variable in Model. I googled and knew this is the Model normal behavior, RoR won't pass the session variable to Model.
So, I would like to use that session variable in validation and also the controller....
how to pass the value of the
session variable into Models? or
is there any other method for my
use case? I need a variable storing
a value, which is required in all
MVCs, and should be independent
between different concurrent users.
Thanks everyone. :)
If I understand you correctly, a session variable changes the way you validate the model. I believe the correct solution for this is the following:
class Blog < ActiveRecord::Base
attr_accessor :validate_title
validate_presence_of :title, :if => :validate_title
end
class BlogsController < ApplicationController
def new
#blog = Blog.new
#blog.validate_title = session[:validate_title]
end
end
The code has not been testet, but that's the idea. The if argument can be the name of a method and you can do whatever you want in there. You can have various validation modes if you want. For example:
class Blog < ActiveRecord::Base
attr_accessor :validation_mode
validate_presence_of :title, :if => :validate_title
def validate_title
validation_mode == "full" or validation_mode == "only_title"
end
end
class BlogsController < ApplicationController
def new
#blog = Blog.new
#blog.validate_mode = session[:validate_mode]
end
end
For more information, read the guide on validation.
Do you need the session variable as part of your model, or just as a flag to determine what to execute?
If the former, you don't need to know where did the parameters originate, just pass the variable as an argument for some call in your method. For example:
#user = User.new(params[:user].merge(:some_attribute => session[:some_key])
and just add the validation as usual in the model.
If you need that to control some flow of the execution, as you mention that should be different for different users, you may need an instance variable in your controller. Something like
class SomeController
before_filter :set_some_session_variable
def set_some_session_variable
#some_variable = session[:some_key]
end
end
You could use session[:some_key] directly in your view, but is better to set it in an instance variable instead.
Best way of doing this is to Create a method with an argument and pass session as an argument.
Fro example
class User < ActiveRecord::Base
def self.some_method(some_arg)
User.find(some_arg)
end
end
from controller
User.some_method(session[:user_id])
Models are an interface to the database. They can be used without a session (e.g. from IRB). It would be a violation of MVC to allow the models to talk to the session. Can you expand your question with a bit more info about what you're trying to do? There is most probably a better way to do it.

Determine the domain in an ActiveRecord 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.

Tracking model changes in Rails, automatically

In my rails app I would like to track who changes my model and update a field on the model's table to reflect.
So, for example we have:
class Foo < ActiveRecord::Base
before_create :set_creator
belongs_to :creator, :class_name => "User"
protected
def set_creator
# no access to session[:user_id] here...
end
end
What's a good testable way for me to get at the user_id from my model? Should I be wacking this data in Thread.current ?
Is it a better practice to hand this information from the controller?
Best practice in MVC is to have your Models be stateless, the controller gets to handle state. If you want the information to get to your models, you need to pass it from the controller. Using a creation hook here isn't really the right way to go, because you are trying to add stateful data, and those hooks are really for stateless behavior.
You can pass the info in from the controller:
Foo.new(params[:foo].merge {:creator_id => current_user.id})
Or you can create methods on User to handle these operations:
class User
def create_foo(params)
Foo.new(params.merge! {:creator_id => self.id})
end
end
If you find yourself writing a lot of permissions code in the controller, I'd go with option 2, since it will let you refactor that code to the model. Otherwise option 1 is cleaner.
Omar points out that it's trickier to automate, but it can still be done. Here's one way, using the create_something instance method on user:
def method_missing(method_sym, *arguments, &block)
meth = method_sym.to_s
if meth[0..6] == "create_"
obj = meth[7..-1].classify.constantize.new(*arguments)
obj.creator_id = self.id
else
super
end
end
You could also override the constructor to require user_ids on construction, or create a method inside ApplicationController that wraps new.
There's probably a more elegant way to do things, but I definitely don't like trying to read state from inside Model code, it breaks MVC encapsulation. I much prefer to pass it in explicitly, one way or another.
Yeah, something like that would work, or having a class variable on your User model
cattr_accessor :current_user
Then in your controller you could have something like:
User.current_user = current_user
inside a before filter (assuming current_user is the logged in user).
You could then extend AR:Base's create/update methods to check for the existence of a created_by/updated_by field on models and set the value to User.current_user.
I'd create new save, update, etc methods that take the user_id from everything that calls them (mainly the controller).
I'd probably extend ActiveRecord:Base into a new class that handles this for all the models that need this behaviour.
I wouldn't trust Thread.current, seems a bit hackish. I would always call a custom method which takes an argument:
def create_with_creator(creator, attributes={})
r = new(attributes)
r.creator = creator
r.save
end
As it follows the MVC pattern. The obviously inherient problem with this is that you're going to be calling create_with_creator everywhere.
You might find PaperTrail useful.
Probably you could check out usertamp plugins, found two in github
http://github.com/delynn/userstamp/tree/master
http://github.com/jnunemaker/user_stamp/tree/master

Resources