Where and how to put Model query methods in Rails 3? - ruby-on-rails

i'm looking for an elegant way to make a model method call. Here is my query:
where("order_id = ?", argument).first
I can't use this query in a scope, because of the first() method, which is chained after the where() method.
So I tried to create the following class method:
def self.find_order(value)
where("order_id = ?", value).first
end
When I use this method twice in my controller action, I get a warning from my IDE, that I should move this query into a scope. When I rename the method without 'find' in the beginning, I also get a warning in my controller, that I shouldn't call more than one method besides find() or new() (I have a another model method calling in this controller action). I know, it isn't a real problem because it works, but I'm interested in writing nice and dry code. I hope it isn't an annoying question, I should fairly say that I'm relatively new to programming.
Thanks in advance!

Related

Rails why isn't .build working, but .new - is?

I'm creating a simple CRUD in rails like this:
def new
build_sportist
end
private
def build_sportist
#sportist ||= Sportist.build
#sportist.attributes = sportist_params
end
and get the following error: undefined method 'build' for Sportist in the build_sportist method. Since I'm using the build_sportist method for the create, new, edit and update actions, I don't want to set it to .new, also as far as I'm aware of, .new and .build do a very similar thing and this should work.
What could be the possible explenation to this?
The error shows that there's no .build method. If you plan to initialize the Sportist for edit and update action, you'd need to pass the id from params to get the right model. Then for create, you need a new object.
So for actions that handle an existing sportist, you'd need
#sportist = Sportist.find(params[:id])
and for actions like create or new, where you need a new object
#sportist = Sportist.new
These can be in separated methods run by before_filter hooks for the concerned actions, or on the action method itself.
If you want to unify these, you can play with #assign_attributes to set the posted params and then handle the save on each action so that you control the experience for failures and success.
In my experience, this is the type of encapsulation that isn't worth much as you lose a lot of readability and encapsulate very little business logic.

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.

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.

Adding variables from object passed from model to controller

I have a Person model. When a new Person is created, I want to set a random_variable in the controller and pass it as part of the #person object. I have tried
Model
attr_accessor :random_variable
Controller:
def create
#person = Person.new(params[:person])
#person.random_variable = 'A Random string'
#person.save
end
Unfortunately, if I try and access self.random_variable in the model, this doesn't work (I get nil).
Can someone explain why this doesn't work, and how to go about doing it? (and yes, I know this doesn't really hold with MVC convention, but the only other way of doing what I need is a very heavy non-dry controller)
What you are describing should work. How are you determining that the random variable is in fact nil? Have you tried using the debugger?
Have you tried just using update_attribute?
update_attribute(:random_variable, 'A Random string')
Or, maybe jump into script/console to see exactly what's happening.
Something weird is going on. I should have explained, as Brad suggested, that I am trying to access random_variable inside a setter method for another object. I had understood that params was handed to the controller, than the model only accessed once #person.save was called. For some reason the setter seems to be being hit before the controller.
I am basing this on the fact that when I accidentally put a debugger statement in the model setter method and controller at the same time, the model debugger was triggered first. I am clearly going to have to do some reading unless someone can explain this. Unfortunately I need to get this working tonight, so I will be going for a hack workaround for the time being

problem using sql instead of named scope in rails

I have a method "search" in my model, which depending upon the various parameters passed runs an sql query in which i am joining seven tables. but when i am using this method with another named scope then error is shown "undefined method call for array".
but when instead of this search method if i use group of named scope then it works fine.
so how to integrate named scope with that method
It's a little hard to tell for sure without seeing the code. But it sounds like the search method is intended to be called on the Model class and when you chain the named_scope calls either they don't know what to do with the array your search method supplies or your search method doesn't know what to do with the array of records the names scope supplies.
I think your #search method is probably returning something (an Array?) that does not implement some method that named_scope expects. From a quick look into activerecord/lib/active_record/named_scope.rb it appears that named_scope returns a Scope object, which does implement #call (and a bunch of other non-Array methods too). That looks to be why chaining scopes works. So an Array just isn't going to work.
Could you rework your #search method into a named_scope? I realise that you're going to get an unusually (for me, at least) complex definition, but you should then be able to chain your results with other scopes.
Alternatively, how about making your custom search method work so that it returns (and must also take, for chaining before and after to work) a Scope? Probably haarder to do than a big named_scope though.
hey sorry for replying late, but found the answer
I have stored the query in a variable and then passed to the named scope where i do find_by_sql.
hence got the desired result.

Resources