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.
Related
I have a model defined in a gem (Google::APIClient.new) and I've created an instance of that gem in my controller.
I want to share the instance across controller actions per each user so I need to persist it somehow. I've tried storing it in a sessions variable (session[:client] = Google::APIClient.new) and into a field of one my own models (User.goog_client = Google::APIClient.new) which didn't work. Is there a proper way of persisting a model from another gem per each user?
Thanks in advance!
Soln: Found a simpler soln, store the attributes in sessions then reload them into the model:
session[:access_token] = client.authorization.access_token
session[:refresh_token] = client.authorization.refresh_token
session[:expires_in] = client.authorization.expires_in
session[:issued_at] = client.authorization.issued_at
client.authorization.access_token = session[:access_token]
client.authorization.refresh_token = session[:refresh_token]
client.authorization.expires_in = session[:expires_in]
client.authorization.issued_at = session[:issued_at]
It sounds like you might want to create a wrapper class for these objects that inherits from ActiveRecord::Base.
The attributes on your wrapper object would be whatever information is required to instantiate the object via the gem. Then you would create (or override) a finder method that does so.
class FooWrapper < ActiveRecord::Base
attr_accessible :x, :y, :z
def self.get_real_foo(wrapper_id)
wrapper_obj = self.find(wrapper_id)
return FooGem.new(wrapper_obj.x, wrapper_obj.y, wrapper_obj.z)
end
end
You say you tried storing the object in the session and your models? How exactly did you go about that? This may not really be the best way to solve your problem... If you post more specifics we will be better able to help you down the right path.
Edit Addition:
If you want the gem instance to be tied to a specific user then make FooWrapper :belongs_to :user. When you instantiate the real gem instance then you use whatever user-specific information as needed.
You can add filter in the controller for example
class YourController < ApplicationConroller
before_filter :get_instance
def action1
#you can use #instance here
end
def action2
#you can use #instance here
end
private: # Hide from outside
def get_instance
#instance = CreateYourGemInstanceHere
end
end
I have a model, for example :
class Account < ActiveRecord::Base
before_create :build_dependencies
def build_dependencies
# use nifty params to build this related object
build_nifty_object(params)
end
The initial params are sent in through a hidden form tag on the Account#new form.
But there's no reason/need for these params to be saved to the account model. I just need them in the NiftyObject model.
Is there a clever way to pass these params to the before_create method ? Or any other alternatives that might accomplish the same task?
Thanks!
You can use instance variables to workaround this, and do +1 step from the controller:
class Account < ActiveRecord::Base
before_create :build_dependencies
def assign_params_from_controller(params)
#params = params
end
def build_dependencies
# use nifty params to build this related object
build_nifty_object(#params)
end
In the controller:
def Create
account = new Account(params)
account.assign_params_from_controller( ... )
account.save # this will trigger before_create
end
I believe the active-record callback cycle hurts you in most cases, this included. Instead of using before_create, I recommend you use a service object that coordinates the creation of Account and nifty-object.
I assume you want nifty-object to know about the account, so I passed it in to it's create method.
class CreatesAccount
def self.create(params)
account = Account.new(params)
return account unless account.valid?
ActiveRecord::Base.transaction do
account.save!
NifyObject.create(params, account: account)
return account
end
end
end
I appreciate everyone's answers. But I finally am going with an attr_accessor instead. In this way, it doesn't save anything anywhere, but would still be accessible from the model in a before_create method.
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.
Oddly enough, most of this works as it has been written, however I'm not sure how I can evaluate if the current_user has a badge, (all the relationships are proper, I am only having trouble with my methods in my class (which should partially be moved into a lib or something), regardless, the issue is specifically 1) checking if the current user has a record, and 2) if not create the corresponding new record.
If there is an easier or better way to do this, please share. The following is what I have:
# Recipe Controller
class RecipesController < ApplicationController
def create
# do something
if #recipe.save
current_user.check_if_badges_earned(current_user)
end
end
So as for this, it definitely seems messy, I'd like for it to be just check_if_badges_earned and not have to pass the current_user into the method, but may need to because it might not always be the current user initiating this method.
# User model
class User < ActiveRecord::Base
def check_if_badges_earned(user)
if user.recipes.count > 10
award_badge(1, user)
end
if user.recipes.count > 20
award_badge(2, user)
end
end
def award_badge(badge_id, user)
#see if user already has this badge, if not, give it to them!
unless user.badgings.any? { |b| b[:badge_id] == badge_id}
#badging = Badging.new(:badge_id => badge_id, :user_id => user)
#badging.save
end
end
end
So while the first method (check_if_badges_earned) seems to excucte fine and only give run award_badge() when the conditions are met, the issue happens in the award_badge() method itself the expression unless user.badgings.any? { |b| b[:badge_id] == badge_id} always evaluates as true, so the user is given the badge even if it already had the same one (by badge_id), secondly the issue is that it always saves the user_id as 1.
Any ideas on how to go about debugging this would be awesome!
Regardless of whether you need the current_user behavior above, award_badge should just be a regular instance method acting on self instead of acting on the passed user argument (same goes for check_if_badges_earned). In your award_badge method, try find_or_create_by_... instead of the logic you currently have. For example, try this:
class User < ActiveRecord::Base
# ...
def award_badge(badge_id)
badgings.find_or_create_by_badge_id(badge_id)
end
end
To access the current_user in your model classes, I sometimes like to use thread-local variables. It certainly blurs the separation of MVC, but sometimes this kind of coupling is just necessary in an application.
In your ApplicationController, store the current_user in a thread-local variable:
class ApplicationController < ActionController::Base
before_filter :set_thread_locals
private
# Store thread-local variables so models can access them (Hackish, but useful)
def set_thread_locals
Thread.current[:current_user] = current_user
end
end
Add a new class method to your ActiveRecord model to return the current_user (you could also extend ActiveRecord::Base to make this available to all models):
class User < ActiveRecord::Base
def self.current_user
Thread.current[:current_user]
end
end
Then, you'll be able to access the current user in the instance methods of your User model with self.class.current_user.
What you need to do first of all is make those methods class methods (call on self), which avoids needlessly passing the user reference.
Then, in your award_badge method, you should add the Badging to the user's list of Badgings, e.g.: user.badgings << Badging.new(:badge_id => badge_id)
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