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
Related
Draper recommends decorating objects at the end of a controller method or alternatively using their decorates_associated method to automatically decorate them.
I like the idea of the view explicitly declaring what it expects to receive (granted the rest of Rails doesn't work like this but it still feels nice). I'd therefore prefer to decorate objects at the top of the view rather than at the end of the controller:
So in users#show I would like to do:
- #user = #user.decorate
%h1= #user.full_name
%p= #user.description
Instead of decorating in the users controller like this
class UsersController < ApplicationController
...
def show
#user = User.find(params[:id]
#user = #user.decorate
end
end
I suspect there's some caching issue with doing things this way round but it feels nice. What if anything am I missing? Is what I'm proposing bad practice?
I think this is a very good question, because draper confuses the traditional MVC logic a bit. As you can read in the draper README which you have linked, draper aims to replace cluttered helper definitions with a more structured approach.
However, to which part of M-V-C does this belong to? Draper claims to decorate objects, which can be associated to the representation part of the software architecture and therefore neither belongs to the Model nor does it really fit into the Controller.
Following #Damien Roche's comment, it also does not really fit into a View, because one usually does not expect a template file to perform any more actions on an object than displaying its properties.You also do not define your helpers in the template, right?
To my mind, draper can be seen more like an intermediary that extends the objects a controller has selected for displaying before they reach the view. Following that logic I prefer the usage of the decorate_assigned command, because that places the decorations somehow between Controller and View.
Additional remark: I know what you mean with 'the view declaring what it wants to receive' but this stands in contrast to the fact, that you usually define one decoration per model for the whole application. So there is not so much room for 'special requests'
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
I understand that it is best practice to refactor as much of my code as possible into the models, however I am relatively new to rails, and programming as a whole. One of the concepts that seems to be causing me a little trouble is the nature of models, and understanding the scope or the availability of methods and variables.
First of all with a typical method written in the model what are the limitations (scope) that your method can be called on? How does the .self aspect work? What controllers / views have access to methods defined in the model?
I understand that these are rather basic principles but I believe that my “assumptions” with regard to this are causing me all manner of problems.
In Model-View-Controller (MVC):
A model holds your data, and any functionality closely related to your data (low level logic)
A controller holds your business logic (high level application logic)
A view holds your presentation layer (user interface)
Views have access to any public model methods. (Note: all ruby methods are public by default.) Of course, the model object must be instantiated first in the appropriate controller method, and must be instance variables (i.e. #person) and not local variables (i.e. person) in the controller.
Controllers also have access to any public model methods.
Protected methods limit access to within the class or within any of its children. Private methods limit access to within the class, only.
It appears to me that class methods, i.e. def Person.some_method ..., are visible anywhere whether or not they are defined as public, protected, or private, although this is counter-intuitive.
Regarding your question about self... You can use self for all calls to the model's own methods from inside that model, and you won't go wrong.
e.g. for Person model having first_name and last_name columns:
class Person < ActiveRecord::Base
def full_name
"#{self.first_name} #{self.last_name}"
end
def parse_name full
self.first_name, self.last_name = full.split
end
end
However, that's overkill. You actually don't need to use self for retrieving attributes in ActiveRecord, only for setting attributes, so the following is fine:
class Person < ActiveRecord::Base
def full_name
"#{first_name} #{last_name}"
end
def parse_name full
self.first_name, self.last_name = full.split
end
end
Based on your questions, it would appear you need to read up on Object-Oriented Programming and the MVC pattern. These are not exclusive to Rails, of course.
In short, there is no real limitation in what you can use to access the Model.
Technically you can call methods from your models in your views and your controllers, or other models.
Here's how I look at it:
Models = Your application's logic
Views = Front-end for your models, tying in the pieces that you want the user (or service) to see
Controllers = Glue for Views and Models, calls model data and hands it to the view
You should shy away from calling your models directly from your view.
If you are performing more than 1 or 2 operations on an object in your controller you should probably move it into a model method.
From here, I'd reccomend you picking up a book to learn about what classes do, instance methods, class methods, etc.
I recommend "Learn to Program" from Pragmatic Programmers.
http://pragprog.com/book/ltp2/learn-to-program
From there learn about what MVC is (lots of information out there), which will help with how you understand models, views, controllers, and how they relate.
Hey Rails newbie here.
I used to have a lot of stuff going on in one of my controllers. Someone told me that its good practice to have "fat models and thin controllers" So I was moving some things over to the model.
In my controller's show method I used to have some # variables that I would use in my view. Now I have those variables in a method in my model. Will I still be able to access those in my view? If so do I have to make any adjustments?
Thanks
You will have to create an instance of your model in the controller as an # variable. You can then call the methods from that inside the view.
e.g. imagine you used to have some long bunch of logic in your controller which calculated a credit score for a customer culminating in
#credit_score = credit_score
and you've now moved this into a credit_score method on the Customer model.
You now just need
#customer = Customer.find...
in the controller
and you can the use <%= #customer.credit_score %> within the view.
This is what people mean by fat models and thin controllers. If you'd like some more advice then it's best to update the question with some specifics from your app.
The common practice is to define the variables of this kind in controllers:
#object = Model.new
to later use it in form_for or something like that. Some people use Model.new directly in views instead. That's somewhat unusual but still makes sense, especially knowing that Rails just loops through all of the instance variables in your controller every time to make them available in your views
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.