I joined Rails team and maintain the codes.
Some of the objects are controlled by Gem virtus, but I really don't understand like below code is doing.
I understand the result that the attribute 'latest_book' can collect latest book from Books but why it can be done? What 'books=(books)' is doing? and Why 'super books' is here?
class GetBooks
include Virtus.model
include ActiveModel::Model
attribute :books, Array[Book]
attribute :latest_book, Book
def books=(books)
self.latest_book = books.sort_by { |book| book['createdate'] }.last
super books
end
end
Could you help me?
def books=(books) is defining a method called books= which takes a single argument books. Yes, that's confusing. It should probably be def books=(value) or def books=(new_books).
And yes, the = is part of the method name. self.books = value is really syntax sugar for self.books=(value). Again, the method is books=.
super books is super(books). super calls the next inherited or included method of the same name; it's calling books= created by attribute :books, Array[Book]. This is a "method override" which allows you to add to the behavior of an existing method.
When books= is called it updates latest_books and then calls its the original method to set the books attribute.
gb = GetBooks.new
gb.books = [old_book, new_book]
p gb.latest_book # new_book
p gb.books # [old_book, new_book]
Related
Rails newbie wondering why the find method is called on the Article class but not the instanced model.
#article = Article.find(params[:id])
It makes sense to me to do Article.new since we are instantiating an object from the Article class but why do we call the find method on the Article class even for Article.all?
Article is associated with the whole collection. And it is logical to have find on Collection to find a record from the collection. It is more clearly than having Article.new.load(params[:id]).
More than that it's considered to be a common practice, consider next example:
class Article
##instances = []
attr_accessor :name
def initialize(name)
#name = name
##instances << self
end
def self.find(name)
##instances.find { |i| i.name == name }
end
end
article = Article.new('New Article')
Article.find('New Article') #=> #<Article:0x00000001668040>
About Rails. find is one of those methods which can be called on a model or on a Relation. Some of those methods do even return Relation, for example all, where, order, limit and offset. And it is nice because you can form a chain:
Article.where(..) #=> Relation
Article.where(..).find(12) #=> record with id: 12 satisfying some additional requirements
I have N number of tables and N number of functions. All functions have same code only table name changes. Can I make a common function to be used by all of these function.
Something like this
def funcN
common_func(tableN)
end
private
def common_func(tablename)
"Some Code"
end
I know there may be multiple ways.. What are the possible ways to do it?
You are very close. Just pass a table name as an argument to funcN:
def funcN(tableN)
common_func(tableN)
end
private
def common_func(tablename)
"Some Code"
end
What are all the possible ways to do it?
Theoretically there are indefinite number of ways to solve some problem, so you will never get an answer to this question.
P.S. Your naming does not follow the conventions. Here is how it would look if it did:
def func_n(table_name)
common_func(table_name)
end
private
def common_func(table_name)
# code omitted
end
If model name is static in funcN then just pass it as the string for example consider post then funcN("Post") or from a rails record funcN(#record.class.to_s)
in private method catch the string param as yours tablename and you can convert it into model by myModel = tablename.constantize
then you can carry on with your line of code on that model myModel
If the function, usually called a method in Ruby, is inside a model it can reference the table_name. You can share the common code using a module and including it in each model which needs it, such as:
class Person < AR::Base
include CommonCode
end
class Fruit < AR::Base
include CommonCode
end
module CommonCode
def do_something
self.table_name
end
end
Person.new.do_something # => 'people'
Fruit.new.do_something # => 'fruits'
I'm trying to optimise database queries so have been adding Model.includes(:related_model) where appropriate.
What is the appropriate way use this within methods inside my model? For example if I have a method in my model like:
def some_method
self.child_models.each do |child_model|
total_score += child_model.attribute
end
end
How do I use includes in instances like this? It seems natural to do it like this but it doesn't work:
def some_method
self.includes(:child_model).child_models.each do |child_model|
total_score += child_model.attribute
end
end
Most times when I produce an n+1 query it seems I'm referencing the model self but I can't seem to find any examples of this.
Thanks!
You are using self in an instance method so self is the instance of your class but includes is a class method. You need to use your original sample code to use includes Model.includes(:related_model). I think what you really want is:
def some_method
self.child_models.sum('attribute')
end
I would use includes when I am building conditions in a relation not looking at the children of an instance.
Let's say I have a model called Article:
class Article < ActiveRecord::Base
end
And then I have a class that is intended to add behavior to an article object (a decorator):
class ArticleDecorator
def format_title
end
end
If I wanted to extend behavior of an article object, I could make ArticleDecorator a module and then call article.extend(ArticleDecorator), but I'd prefer something like this:
article = ArticleDecorator.decorate(Article.top_articles.first) # for single object
or
articles = ArticleDecorator.decorate(Article.all) # for collection of objects
How would I go about implementing this decorate method?
What exactly do you want from decorate method? Should it simply add some new methods to passed objects or it should automatically wrap methods of these objects with corresponding format methods? And why do you want ArticleDecorator to be a class and not just a module?
Updated:
Seems like solution from nathanvda is what you need, but I'd suggest a bit cleaner version:
module ArticleDecorator
def format_title
"#{title} [decorated]"
end
def self.decorate(object_or_objects_to_decorate)
object_or_objects_to_decorate.tap do |objects|
Array(objects).each { |obj| obj.extend ArticleDecorator }
end
end
end
It does the same thing, but:
Avoids checking type of the arguments relying on Kernel#Array method.
Calls Object#extend directly (it's a public method so there's no need in invoking it through send).
Object#extend includes only instance methods so we can put them right in ArticleDecorator without wrapping them with another module.
May I propose a solution which is not using Module mixins and thereby granting you more flexibility. For example, using a solution a bit more like the traditional GoF decorator, you can unwrap your Article (you can't remove a mixin if it is applied once) and it even allows you to exchange the wrapped Article for another one in runtime.
Here is my code:
class ArticleDecorator < BasicObject
def self.[](instance_or_array)
if instance_or_array.respond_to?(:to_a)
instance_or_array.map {|instance| new(instance) }
else
new(instance_or_array)
end
end
attr_accessor :wrapped_article
def initialize(wrapped_article)
#wrapped_article = wrapped_article
end
def format_title
#wrapped_article.title.upcase
end
protected
def method_missing(method, *arguments)
#wrapped_article.method(method).call(*arguments)
end
end
You can now extend a single Article by calling
extended_article = ArticleDecorator[article]
or multiple articles by calling
articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]
You can regain the original Article by calling
extended_article.wrapped_article
Or you can exchange the wrapped Article inside like this
extended_article = ArticleDecorator[article_a]
extended_article.format_title
# => "FIRST"
extended_article.wrapped_article = article_b
extended_article.format_title
# => "SECOND"
Because the ArticleDecorator extends the BasicObject class, which has almost no methods already defined, even things like #class and #object_id stay the same for the wrapped item:
article.object_id
# => 123
extended_article = ArticleDecorator[article]
extended_article.object_id
# => 123
Notice though that BasicObject exists only in Ruby 1.9 and above.
You'd extend the article class instance, call alias_method, and point it at whatever method you want (although it sounds like a module, not a class, at least right now). The new version gets the return value and processes it like normal.
In your case, sounds like you want to match up things like "format_.*" to their respective property getters.
Which part is tripping you up?
module ArticleDecorator
def format_title
"Title: #{title}"
end
end
article = Article.top_articles.first.extend(ArticleDecorator) # for single object
Should work fine.
articles = Article.all.extend(ArticleDecorator)
May also work depending on ActiveRecord support for extending a set of objects.
You may also consider using ActiveSupport::Concern.
Let's say I have a class like:
class Basket < ActiveRecord::Base
has_many :fruits
Where "fruits" is an STI base class having subclasses like "apples", "oranges", etc...
I'd like to be able to have a setter method in Basket like:
def fruits=(params)
unless params.nil?
params.each_pair do |fruit_type, fruit_data|
fruit_type.build(fruit_data)
end
end
end
But, obviously, I get an exception like:
NoMethodError (undefined method `build' for "apples":String)
A workaround I thought of works like this:
def fruits=(params)
unless params.nil?
params.each_pair do |fruit_type, fruit_data|
"#{fruit_type}".create(fruit_data.merge({:basket_id => self.id}))
end
end
end
But that causes the Fruit STI object to be instantiated before the Basket class, and so the basket_id key is never saved in the Fruit subclass (because basket_id doesn't exist yet).
I'm totally stumped. Anyone have any ideas?
Instead of adding a setter method in Basket, add it in Fruit:
class Fruit < ActiveRecord::Base
def type_setter=(type_name)
self[:type]=type_name
end
end
Now you can pass the type in when you build the object through an association:
b = Basket.new
b.fruits.build(:type_setter=>"Apple")
Note that you can't assign :type this way, since it is protected from mass assignment.
EDIT
Oh, you wanted to run different callbacks depending on the subclass? Right.
You could do this:
fruit_type = "apples"
b = Basket.new
new_fruit = b.fruits << fruit_type.titleize.singularize.constantize.new
new_fruit.class # Apple
or define a has_many association for each type:
require_dependency 'fruit' # assuming Apple is defined in app/models/fruit.rb
class Basket
has_many :apples
end
then
fruit_type = "apples"
b = Basket.new
new_fruit = b.send(fruit_type).build
new_fruit.class # Apple
In Ruby terms, "#{x}" is simply equivalent to x.to_s which for String values is exactly the same as the string itself. In other languages, like PHP, you can de-reference a string and treat it as a class, but that's not the case here. What you probably mean is this:
fruit_class = fruit_type.titleize.singularize.constantize
fruit_class.create(...)
The constantize method converts from a string to the equivalent class, but it is case sensitive.
Keep in mind that you're exposing yourself to the possibility someone might create something with fruit_type set to "users" and then go ahead and make an administrator account. What's perhaps more responsible is to do an additional check that what you're making is actually of the right class.
fruit_class = fruit_type.titleize.singularize.constantize
if (fruit_class.superclass == Fruit)
fruit_class.create(...)
else
render(:text => "What you're doing is fruitless.")
end
One thing to watch out for when loading classes this way is that constantize will not auto-load classes like having them spelled out in your application does. In development mode you may be unable to create subclasses that have not been explicitly referenced. You can avoid this by using a mapping table which solves the potential security problem and pre-loading all at once:
fruit_class = Fruit::SUBCLASS_FOR[fruit_type]
You can define this constant like this:
class Fruit < ActiveRecord::Base
SUBCLASS_FOR = {
'apples' => Apple,
'bananas' => Banana,
# ...
'zuchini' => Zuchini
}
end
Using the literal class constant in your model will have the effect of loading them immediately.