Need help with a code smell - ruby-on-rails

In my Rails application I'm trying to make the controllers skinnier and am having difficulty with one object that I keep having to pass around.
The object represents a user's facebook session and is instantiated off of the session so it exists in the controller side of things. Many model methods use it, so it is repeatedly passed in as an argument all over the place.
This smells like something, how to DRY it up the Rails way? Thanks!

First, I would recommend using a system similar to Authlogic for your authentication. This gives you two bonuses:
You have proven, stable, tested authentication for your application
The sessions are based like Models, so you can do this kind of stuff...
class Widget < ActiveRecord::Base
def do_facebook_stuff
UserSession.find #This gets you the current session
UserSession.find.record # This gets your the user for the current session
end
end
Now you no longer need to pass the session information in, as you can do basic model-style lookups to find it. In addition to this, Authlogic has a plugin architecture that supports Facebook Connect, which may help you further.

I can give you the CakePHP way (which was originally designed to be like rails).
All CakePHP models extend the same parent AppModel, and all controllers extend an AppController.
I would make an empty parameter in the AppModel that represents your object. Then in the AppController I would store the object in the current model's parameter, if the object exists. There is a callback in the CakePHP AppController called beforeFilter() which fires before any code in the controller. The ideal place to check for the object and store it in the model would be in whatever equivalent Rails has of this beforeFilter callback.
That is unless all models don't use the object. If that is true, you could put the parameter in only the Models that use it (instead of the parent), and then in the beforeFilter of the AppModel you can check first if the Model has that empty parameter.
I know it's not Ruby, but it would look like this:
public function beforeFilter() {
if (isset($this->{$this->modelName}->yourObjectParameter)) {
$this->{$this->modelName}->yourObjectParameter = $this->yourObject;
}
}
$this->modelName is a string that corresponds to the name of the current model. the { } around $this->modelName in PHP is called complex syntax. It basically converts the string into the model object. Not sure how to do the same thing in Ruby.

You can take your method to application controller, something like this
class ApplicationController < ActionController::Base
before_filter :get_facebook_session
def get_facebook_session
#facebook_session = <your code >
end
end
And you can access #facebook_session variable from your controllers and views
cheers
sameera

Related

Does a controller extend from a model in Rails?

Been working on Rails for a bit I can't get my head wrapped around how there is almost nothing written in a model.rb file when just creating a basic CRUD application.
I was looking at a controller.rb file and was wondering why the controller file has access to model methods like all and create when there seems to be no connection between the two files.
Should't the model object methods like modelname.all and modelname.create etc. be written in the model file instead of the controller file?
TL;DR
No, it doesn't.
General Answer
A controller does not have access to model methods as you think, in the controller you never just write all or create, you write something like User.all or #user.create. You are calling the methods on the model class or instance. You are simply using the model in the controller, but this is not limited to the controller, you could do exactly the same thing in the views if you really wanted to, or you could create custom service objects, or policy objects, or repository objects, and you could still call User.all etc from inside them too.
For a very basic application you are correct you can get by writing very little or no logic, but this is only because Rails provides us with methods and does it all for us (hooray!).
Nothing in a model file just means nothing specific to this particular model... inheriting from ApplicationRecord or ActiveRecord::Base means you have built in all the class methods (all, where, find, find_by, etc) and all the instance methods (new, create, update_attributes, etc) pre-defined for the model.
The controller determines what needs to happen, the model has the methods to make it happen, so
def index
#model = Model.all
end
Means that at the point of displaying a list of all model records, you access the model's class method all

How to call controller's method

I have a functionality that doesn't relate to incoming requests (like get,post,etc).
But I want to follow MVC convention and make it like Model-Controller:
in controller part I will process income requests from another parts, decide from which model it should retrieve information, authorization, filtering and so on;
in model part I will store information, validate it, expire it and etc.
In Rails the controller's actions can be called by income request directed by routes file.
The problem for me that if I've created a controller like this:
class SomeController < ApplicationController
def some_action; end
end
How can I call method some_action from SomeController from any place in my application?
P.S. Calling SomeController.new.some_action doesn't seem right to me, because I think all controllers are parts of app object.
You can do this by instanciating the controller.
SomeController.new.some_action
However this is not really an MVC way. I don't really know what you want to use it for, but probably there is a better way. For example it can be done as in the model, as the modification of data should belong there not to a controller.
I think you should probably create a PORO which will encapsulate this functionality in some method. So that any logic dependent functionality should be within that instead of in the controller. Then you can call them in either controller.

Setting a Session Variable in a Model

With Rails 3, How can you set a session variable in a model
session[:cannot_reason] = "no such item"
I'd like to set the above in my user model. Right now I get this error:
undefined local variable or method `session' for #<User:0x00000102eb9c38>
Ideas? Thanks
There's some unnecessary cargo-culting regarding whether or not models should have access to session data. I think this is silly, since session is really just another form of persistant storage (albeit for a much shorter time frame) and, in Rails, it seems ok to have your domain object also be able to persist itself.
That being said, the not very clean way to do it would be to pass the session hash as a parameter into the User method that will operate on it:
class User < ...
def mymethod(session, count)
session[:count] = count
end
end
In your controller, you would then do something like:
def update
# ...
user.mymethod(session, count)
end
Imagining that mymethod is implementing some business logic, this will modify the session hash appropriately. You don't have to pass the session hash back out to the controller because Ruby passes around references to objects (like a Hash)--modifications are made destructively to those referenced objects.
A word of advice: The above example, in my opinion, is smelly. This is because User is an ActiveRecord model which (I'm assuming) persists to a database and we're adding behavior that makes it also persist to a session cookie. To me, this violates SRP and should be avoided.
The way I would implement this would be to extract the session storage logic out to a separate domain object that encapsulates the reason for its existence. Maybe, for example, count isn't just a "count", it's the total of the number of items in the user's temporary cart.
class TemporaryCart
def initialize(session)
#session = session
end
def add_item
# ... other item adding logic
#session[:temporary_cart][:num_items] += 1
end
end
Now your controller would look like this:
def update
# ...
TemporaryCart.new(session).add_item
end
This is much more revealing and opens the door for an obvious way to abstract out session access code if you find yourself using session storage a lot. Also notice that I namespaced the data in the session hash (but didn't show this implementation). I recommend you do this so you don't step on your own toes when adding other data.
In short, you can't. By design, models don't have access to cookies and the session. If you to access items in the session from your model, you'll need to explicitly pass them in from the controller.
The session object is not visible in models. Either pass it as a parameter to a method in your model (IMHO bad) or define a method in your model which returns what you want and then store it in the session (from your controller).
class User < ...
def item_status
return :no_such_item
end
end
In your controller
session[:item_status] = current_user.item_status

Where should I put this code?

My setup: Rails 2.3.10, Ruby 1.8.7
I need to update multiple instances of a model for a transaction. Should I make a class method and updates all the instances in the method or should I move that logic to the controller and updates each instance via an instance method for the model? I guess it is a tradeoff between fat controller vs. fat model and the general advice is fat model over fat controller.
Neither. If it's a significant piece of logic, why not incorporate it into a dedicated class?
Alternatively, if your (I'm assuming) form data can be configured thus:
params[:models] = { id_of_instance_1 => { :attribute => value },
id_of_instance_2 => { :attribute => value2 },
}
You can quite easily do a group update in your controller with:
Model.update(params[:models].keys, params[:models].values)
More information about the details you're updating and where they're coming from could help.
EDIT: After reading your response below...
There's a few ways you could do it. You could implement Model.win and Model.lose as class methods to incorporate the logic, then simply call those methods from your controller:
def process_outcome
#winner = Model.win(params[:winning_id])
#loser = Model.lose(params[:losing_id])
end
Or, even as a single method call:
def process_outcome
# passing the entire params hash to `process_outcome` which returns an array
#winner, #loser = Model.process_outcome(params)
end
Personally, if the only child objects involved are all instances of the same model, I'd implement this logic within the class itself.
However, if you're bringing a variety of classes into the mix, it might be worth encapsulating it into a separate object altogether:
# app/controllers/models_controller.rb
def process_outcome
#outcome_processor = OutcomeProcessor.new(params)
#winner = #outcome_processor.winner
#loser = #outcome_processor.loser
end
Either way, your actual transaction block should not be in the Controller.
I think you should follow the tradition. :)
You can use a different class (Not controllers) to write transaction method.
It should almost certainly go in the model, not the controller.
I think it should go in an instance method, not a class method. My reasoning behind this is that you're likely going to be calling this via the URL /model/id/action?other_model_id=other_id. Then it would follow that in the controller action you'd get appropriate model instance for id, as well as other_id, but since this is the path for the id model, not the other_id model, you'd call #id_model.perform_action(#other_id_model).
Hope this makes sense.

Where should I store calculated values I use throughout my application in Rails?

I have the following variable definition:
#monday = (Time.now).at_beginning_of_week
I use that in different models, controllers, and views.
Where can I define it (and how should I define it -- ##? ) so I can define it once and use throughout my Rails application?
Should it be in environment.rb? Should it be a ##?
I would add it to application controller:
before_filter :load_date
def load_date
#monday = (Time.now).at_beginning_of_week
end
So it will be accessible in all of yours controllers and views. If you want to use it in your models, then probably you need it as some param, on example for scopes. Then your controller is place where you should pass this variable to model:
#models = MyModel.before_date(#monday)
I don't think you need to have only one instance of this variable for whole application. Initializing it is quite simple. Also it is not good when you initialize it and don't use it. For me it is hard to imagine that you need it in all of your controllers and actions.
The other way is you can define class:
class MyDate
def self.get_monday
Time.now.at_beginning_of_week
end
end
And put it in config/initializers (probably there is better place where to put it). Then you can access it from anywhere in your application:
MyDate::get_monday
Defining this value using environment.rb is not what you want, because the value would be calculated when the server starts and will remain fixed after that. If you want the value to be refreshed every week, this is a problem.
I would go with the class variable (##) in ApplicationController. But this is not accessible in the model.
What you can do is create a new module, define this value in this module, and mixin this module in any controller or model that needs it. So you would have MyModule::start_of_week with the value. Just ensure that this value gets set on every request.

Resources