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.
Related
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
When I call update I don't want to just much update the record as I want to:
Call a model function to update a property of the model.
Update the model.
--
def update
#simulation = Simulation.find(params[:id])
#simulation.next # This is a function that has some logic to changes a property of the simulation record
#simulation.update(simulation_params)
end
Is this the correct way of going about this, or should I be using a separate controller function or another route?
Just for clarity, I personally would create an instance method in Simulation, high level code should be something like..
#Simulation model
class Simulation
....
def next_and_update(attrs)
next
update(attrs)
end
end
#controller
def update
#simulation = Simulation.find(params[:id])
#simulation.next_and_update(simulation_params)
end
Idea is, its ok to have 1-2 lines more, if you can read the code and understand whats happening.
It sounds like "next" is a method that takes care of some behind-the-scenes nitty gritty before an update (or save). That being the case, what you're doing is reasonable. Another controller method or route wouldn't be necessary. If you want to stick with "update" you can.
In this case it won't make a difference whether you finish with update or with save. Update and save both update the record in the database.
Does that help?
I have a details page in Rails 3 app that has several instance variable. For example, my view has the following instances:
#product
#program
#action
...etc...
Some of them are single strings, some are arrays (result of requests from external APIs). Overall, the data comes from at least 3 models.
My question is: What is the best way to consolidate instance variables so my view and my controller look cleaner (without sacrificing readability)? I understand the question is open-ended and everyone has their own preferences, but what are some of the options? I am thinking along the lines of combining the instance variables into an object so the previous code becomes something like this:
Details.product
Details.program
Details.action
Thanks a lot!
UPDATE: Going the object route, is there any good examples (github, blog posts) of actually implementing it?
In my opinion the best way to clean up your controller actions is to assign as few instance variables as possible there. In practice, I try to only assign instance variables in the action/before_filters that are directly derived from the parameters passed to it. Also, in general I try to push as many instance variable assignments to before_filters as I can so as to keep the actions slim.
But the best way to cut down on instance variable assignment in the actions is to use view helpers. For instance if you are currently assigning an instance variable in an action which you use to output some div with, just use a view helper to do the output directly without any need to pass an object to the view.
Here's an example of a before_filter on a controller:
before_filter :assign_variables, :only => :show
def show; end
private
def assign_variables
#product = Product.find(params[:product_id])
#program = Program.find(params[:program_id])
#action = Action.find(params[:action_id])
end
I would suggest not using instance variables at all and go with the decent exposure gem instead.
https://github.com/voxdolo/decent_exposure
http://railscasts.com/episodes/259-decent-exposure
Whenever you find yourself with several instance variables in a controller action, it best to use the presenter pattern.
This video explains everything in details
"Ruby on Rails - Presenters & Decorators"
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.
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.