How to persist a gem model instance? - ruby-on-rails

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

Related

How do you pass data from a controller to a model with Ruby on Rails?

How do you pass data from a controller to a model?
In my application_controller I grab the user's location (state and city) and include a before_filter to make it accesible in all my controllers via
before_filter :community
def community
#city = request.location.city
#state = request.location.state
#community = #city+#state
end
Then I try add the data retrieved in the controller to the model via:
before_save :add_community
def add_community
self.community = #community
end
The data, however, never makes its way from the controller to the model. If I use:
def add_community
#city = request.location.city
#state = request.location.state
#community = #city+#state
self.community = #community
end
The methods request.location.city and request.location.state do not function from the model. I know that everything else is working because if I define #city and #state as strings, under def_community, then everything works, except I don't have a dynamic variable, just a string placed in the model. Also, I know the requests are working in the controller/views, because I can get them to display the proper dynamic info. The issue is simply getting the data from the controller to the model. Thanks a lot for your time.
The concept you're wrestling with is MVC architecture, which is about separating responsibilities. The models should handle interaction with the DB (or other backend) without needing any knowledge of the context they're being used in (whether it be a an HTTP request or otherwise), views should not need to know about the backend, and controllers handle interactions between the two.
So in the case of your Rails app, the views and controllers have access to the request object, while your models do not. If you want to pass information from the current request to your model, it's up to your controller to do so. I would define your add_community as follows:
class User < ActiveRecord::Base
def add_community(city, state)
self.community = city.to_s + state.to_s # to_s just in case you got nils
end
end
And then in your controller:
class UsersController < ApplicationController
def create # I'm assuming it's create you're dealing with
...
#user.add_community(request.location.city, request.location.state)
...
end
end
I prefer not to pass the request object directly, because that really maintains the separation of the model from the current request. The User model doesn't need to know about request objects or how they work. All it knows is it's getting a city and a state.
Hope that helps.
The class instance variables (those that start with #) in the controllers are separate from those in the models. This is the Model vs the Controller in MVC architecture. The Model and Controller (and view) are separated.
You move info from a controller to a model explicitly. In Rails and other object oriented systems, you have several options:
Use function parameters
# In the controller
user = User.new(:community => #community)
# In this example, :community is a database field/column of the
# User model
Docs
Use instance variables attribute setters
# In the controller
user = User.new
user.community = #community
# same as above, :community is a database field
Passing data to models when the data is not a database field
# In the model
class User < ActiveRecord::Base
attr_accessor :community
# In this example, :community is NOT a database attribute of the 
# User model. It is an instance variable that can be used
# by the model's calculations. It is not automatically stored in the db
# In the controller -- Note, same as above -- the controller
# doesn't know if the field is a database attribute or not.
# (This is a good thing)
user = User.new
user.community = #community
Docs

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.

Ruby on Rails - Access controller variable from model

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.

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

How to always set a value for account-scope in Rails?

I'm working on a multi-user, multi-account App where 1 account can have n users. It is very important that every user can only access info from its account. My approach is to add an account_id to every model in the DB and than add a filter in every controller to only select objects with the current account_id. I will use the authorization plugin.
Is this approach a good idea?
What is the best way to always set the account_id for every object that is created without writing
object.account = #current_account
in every CREATE action? Maybe a filter?
Also I'm not sure about the best way to implement the filter for the select options. I need something like a general condition: No matter what else appears in the SQL statement, there is always a "WHERE account_id = XY".
Thanks for your help!
This is similar to a User.has_many :emails scenario. You don't want the user to see other peoples emails by changing the ID in the URL, so you do this:
#emails = current_user.emails
In your case, you can probably do something like this:
class ApplicationController < ActionController::Base
def current_account
#current_account ||= current_user && current_user.account
end
end
# In an imagined ProjectsController
#projects = current_account.projects
#project = current_account.projects.find(params[:id])
I know, I know, if you access Session-variables or Instance variables in your Model you didn't understand the MVC pattern and "should go back to PHP". But still, this could be very useful if you have - like us - a lot of controllers and actions where you don't always want to write #current_account.object.do_something (not very DRY).
The solution I found is very easy:
Step 1:
Add your current_account to Thread.current, so for example
class ApplicationController < ActionController::Base
before_filter :get_current_account
protected
def get_current_account
# somehow get the current account, depends on your approach
Thread.current[:account] = #account
end
end
Step 2:
Add a current_account method to all your models
#/lib/ar_current_account.rb
ActiveRecord::Base.class_eval do
def self.current_account
Thread.current[:account]
end
end
Step 3: Voilá, in your Models you can do something like this:
class MyModel < ActiveRecord::Base
belongs_to :account
# Set the default values
def initialize(params = nil)
super
self.account_id ||= current_account.id
end
end
You could also work with something like the before_validation callback in active_record and then make with a validation sure the account is always set.
The same approach could be used if you always want to add the current_user to every created object.
What do you think?
To answer your second question, check out the new default_scope feature in Rails 2.3.
I understand that you don't want to bother about scoping you account all time. Lets be honest, it's a pain in the a**.
To add a bit magic and have this scoping done seamlessly give a look at the following gem
http://gemcutter.org/gems/account_scopper
Hope this helps,
--
Sebastien Grosjean - ZenCocoon

Resources