Where should I put this code? - ruby-on-rails

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.

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

Fetch an attribute in my model by providing the id

How can i fetch in a model a method that would return the value of a specific model.
The code is has follow
Model Article
def article_title(id)
art = Article.find_by_id(id)
return art.title
end
The view is as follow
<% arttitle = Article.article_title(id) %>
This doesn't work but i am not sure what the way to do it
You've defined an instance method, which should actually work if you called it on an article instance (but that wouldn't make much sense, design-wise). Define it as self.article_title(id) to make it a class method.
Also, it's generally not good practice to define variables and perform lookups in your views. Views are templates to display information - nothing more. The more your business logic is spread through your app in unpredictable ways, the harder it will be to develop and change in the future.
The method should be defined as a class method, with self keyword
def self.article_title(id)
Article.find(id).title
end

Force reload another model's methods in rails?

I have a model that defines methods based off of the entries in another model's table: eg Article and Type. An article habtm types and vice versa.
I define in Article.rb:
Type.all.each do |type|
define_method "#{type.name}?" do
is?(:"#{type.name}")
end
end
This works great! it allows me to ensure that any types in the type db result in the methods associated being created, such as:
article.type?
However, these methods only run when you load the Article model. This introduces certain caveats: for example, in Rails Console, if I create a new Type, its method article.type_name? won't be defined until I reload! everything.
Additionally, the same problem exists in test/rspec: if I create a certain number of types, their associated methods won't exist yet. And in rspec, I don't know how to reload the User model.
Does anyone know a solution here? Perhaps, is there some way to, on creation of a new Type, to reload the Article model's methods? This sounds unlikely.. Any advice or guidance would be great!
I think you'll be better off avoiding reloading the model and changing your api a bit. In Article, are you really opposed to a single point of access through a more generic method?
def type?(type)
return is? type if type.is_a? String # for when type is the Type name already
is? type.name # for when an instance of Type is passed
end
If you're set on having separate methods for each type, perhaps something like this would work in your Type class
after_insert do
block = eval <<-END.gsub(/^ {6}/, '')
Proc.new { is? :#{self.name} }
END
Article.send(:define_method, "#{self.name}?", block)
end

Do methods that help find data in a db such as "find", "find_by", "where" etc belong in the model or controller in ruby on rails?

I've finished a personal project now just going through my code cleaning things up. I'm wondering if methods that help find things in the database belong in the model?
E.g.
This was in my controller:
#user = User.find_by_username(username)
I then moved it to my model:
class << self
def find_user_by_username(username)
User.find_by_username(username)
end
end
added this to my controller:
#user = find_user_by_username(username)
Is there anything wrong with this? does it really matter if I have find, where and other methods that help find things in my controller? What about putting them in helpers?
Another thing is I tried to call that same method in a show action and pass in params with a users username as the value. I get:
undefined method `find_user_by_username' for #<UsersController:0x000001034a6060>
I just want to clean up but not break things. I don't understand why that method would work fine in my new action but not in show action.
Thanks in advance
kind regards
In a good design, you want to have the skinniest controller possible, and that means moving a maximum of code from the controlelr to the model. Then, if your model become too large, there are other technique to move code down the model to other layers (libs, observers, etc).
The find_by_* method is already in the model but it his a class method. So it's perfectly reasonable to call it from your controller.
If your search was not a simple find but , let's say, a search by user.username or user.company.name , then you would probably have to make that search method in the model and call it from your controller.
This way also allows you to call that method from different controller instead of copy/paste-ing it
More info on where to put your code can be found here : http://qualityonrails.com/archives/33
The controller is the perfect place for calls to your model's methods. It's not the perfect place for model logic, though.
Hint: the perfect place for that begins with 'M', ends with 'odel'.
The controller should call User.find_by_username. There should be no find_by_username method for the controller itself, because that's one layer of abstraction too many and "hides" what exactly the find_by_username method is doing.
Call the model method from your controller. You're obsessing about cleaning up when you don't need to.
Your original code is perfectly fine, it is only when you starting chaining methods in the queries that you need to consider refactoring.
ie Refactoring this
User.where(:age => 0..25).where(:owns_a_dog => true).includes(:dogs)
into
User.young_dog_owners
The reason you're getting an undefined method error is because what you defined is still a class method, so you need to call it as such:
#user = User.find_user_by_username(username)
However, you'll notice this isn't really any better than:
#user = User.find_by_username(username)
In general, my feeling is that a simple find(id) or find_by_xxxx(xxxx) is ok to have in a controller, but more advanced logic should be moved to the model. For example, if you have something like User.where(:activated => true).where("created_at > ?", Date.today - 1.week) you would probably want that to be moved to your User model under a find_recent_users method or something.

Need help with a code smell

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

Resources