I'm reading a book called RailsAntiPatterns. In the converter method below, a new OrderConverter object is instantiated, and I assume self refers to an instance of Order class.
# app/models/order.rb
class Order < ActiveRecord::Base
def converter
OrderConverter.new(self)
end
end
# app/models/order_converter.rb
class OrderConverter
attr_reader :order
def initialize(order)
#order = order
end
def to_xml # ...
end
def to_json # ...
end
def to_csv # ...
end
def to_pdf # ...
end
end
Why instantiate a new class inside of converter?
Why does self need to be passed as an argument?
Can you summarize in lay terms what's going on?
Why instantiate a new class inside of converter?
Of course, it's up to the choice of the author, but it's probably convenient. For instance:
#my_order = Order.new
#my_order.converter.to_xml
That reads quite nicely, which is important in the eyes of a Rubyist. As the original designer of Ruby, Yukihiro Matsumoto (Matz) has said:
But in fact we need to focus on humans, on how humans care about doing programming or operating the application of the machines. We are the masters. They are the slaves.
Readibility for humans is, therefore, important if you wish to produce elegant Ruby code.
Why does "self" need to be passed as an argument?
Quite simply, OrderConverter requires an order to convert. Since the method converter is defined for instances of the Order class, an instance that wishes to convert itself will pass self as the argument to OrderConverter#new.
can you summarize in lay terms what's going on?
I hope the above has done that for you.
There's not much happening here.
def converter
OrderConverter.new(self)
end
this method creates a new OrderConverter and returns it. OrderConverter is passed a reference to the Order (self) that it can use to do its work (converting).
That's basically it.
he's returning a new instance of OrderConverter whenever you call the instance method "converter" from the Order class (it's an implicit return).
the constructor from OrderConverter takes an instance of Order as its first argument.
regarding the "why" questions, there's no real answer as far as Ruby is concerned, it's up to the implementator -i.e. the author- what the code actually does.
Related
Developing in Rails 5.2.2.1. I want to define a "global" rescue handler for my model, so that I can catch NoMethodError and take appropriate action. I find that controllers can do this with rescue_from, but models cannot. Knowing that the Rails Developers are smart people ;) I figure there must be some Good Reason for this, but I'm still frustrated. Googling around, and I can't even find any examples of people asking how to do this, and other people either telling them how, or why they can't, or why they shouldn't want to. Maybe it's because rescue handlers can't return a value to the original caller?
Here's what I'm trying to do: I need to refactor my app so that what used to be a single model is now split into two (let's call them Orig and New). Briefly, I want to make it so that when an attribute getter method (say) is called against an Orig object, if that attribute has moved to New, then I can catch this error and call new.getter instead (understanding that Orig now belongs_to a New). This solution is inspired by my experience doing just this sort of thing with Perl5's AUTOLOAD feature.
Any ideas of how to get this done are much appreciated. Maybe I just have to define getters/setters for all the moved attributes individually.
Overide method_missing :) !?
You could try overriding the method_missing method. This could potentially cause confusing bugs, but overriding that method is definitely used to great effect in at least one gem that i know of.
I didn't want to call the class new because it is a reserved keyword and can be confusing. So I changed the class name to Upgraded.
This should get you started.
class Upgraded
def getter
puts "Congrats, it gets!"
end
end
class Original
def initialize
#new_instance = Upgraded.new
end
def method_missing(message, *args, &block)
if message == :attribute_getter
#new_instance.send(:getter, *args, &block)
else
super
end
end
def respond_to_missing?(method_name, *args)
method_name == :attribute_getter or super
end
end
c = Original.new
c.attribute_getter
You will have to change names of the getter and setter methods. Because you have a belongs_to association you can just use that.
Or you could try just using delegate_to
like #mu_is_too_short suggests, you could try something like this?
class Original < ApplicationRecord
belongs_to :upgraded
delegate :getter_method, :to => :upgraded
end
class Upgraded < ApplicationRecord
def getter_method
end
end
Apparently what I needed to know is the word "delegation". It seems there are a variety of ways to do this kind of thing in Ruby, and Rails, and I should have expected that Ruby's way of doing it would be cleaner, more elegant, and more evolved than Perl5. In particular, recent versions of Rails provide "delegate_missing_to", which appears to be precisely what I need for this use case.
I have an instance variable in an active record class called hash_value. It's a serialized hash.
I want to display the hash as XML. Is it right to call hash_value.to_xml? Many nodes are numbers, and XML tags are not allowed to be only number (or start with a number).
I want to override the to_xml method of hash_value. I don't want to override on all hashes, just the hash that's in this record.
class ProductVersion < ActiveRecord::base
serialize :hash_value, Hash
def hash_value.to_xml
end
end
I tried the answer here redefining a single ruby method on a single instance with a lambda
but it doesn't seem to be applicable. I suspect because when I load the record, it creates a new hash_value object and thus the singleton adjustment on the original is moot.
Any thoughts would be great.
I know I could write a function hash_value_to_xml, but I'd rather avoid doing something like that.
Thanks to the first comment, I came up with a solution. Not a good one, but one that works. I'd love to see if there's a better way, because this one smells a bit.
class MyHash < Hash
def to_xml
1/0 #to see if it's run.
end
end
def hash_value
MyHash.new().merge( attributes['hash_value'] );
end
I would personally go for hash_value_to_xml route. But since you insist, here's an idea that might work (haven't tested that)
class ProductVersion < ActiveRecord::base
serialize :hash_value, Hash
alias_method :old_hash_value, :hash_value
def hash_value
h = old_hash_value
h.define_singleton_method(:to_xml) do
# your custom xml logic here
end
h
end
end
The idea is that you intercept value returned from hash_value and patch it on the fly.
I'm reading the Rails AntiPatterns book, which I'm enjoying a lot. At one point, the author talks about the goodness of composition and it gives an example where an Order class gives the responsibility of conversion (to other formats) to another class, called OrderConverter. The classes are defined as:
class Order < ActiveRecord::Base
def converter
OrderConverter.new(self)
end
end
class OrderConverter
attr_reader :order
def initialize(order)
#order = order
end
def to_xml
# ...
end
def to_json
# ...
end
...
end
And then the author says: "In this way, you give the conversion methods their own home, inside a separate and easily testable class. Exporting the PDF version of an order is now just a matter of call-ing the following:"
#order.converter.to_pdf
Regarding to this, my questions are:
Why do you think that order object is preceded by an #? Shouldn't it be created as:
order = Order.new
And then convert by doing:
order.converter.to_pdf
Why is the attr_reader :order line needed in the OrderConverter? It's so we can access the order from an OrderConverter object? Is it needed to be able to do
order.converter.to_pdf ? We could do that without that attr_reader right?
An instance of Order is passed to the initialize method and stored as an instance variable (using the # syntax : #order). This way, this variable can be accessed from other methods in the converter (the variable has the instance scope) :
class OrderConverter
def to_pdf
#order.items.each do |item|
# Write the order items to the PDF
end
end
end
The attr_reader is not strictly required, but is a convenient way to access the Order object from other methods :
class OrderConverter
def to_pdf
order.items.each do |item|
# Write the order items to the PDF
end
end
end
It will also allow you to get the reference to the order out of any converter instance :
converter.order
The # on the front of the variable makes it an instance variable. If it wasn't there the variable would just be a local variable. I'm guessing that since this is a book about Rails, it's assuming that this code would be in a controller. Variables that controllers want to share across methods or expose in their views need to be instance variables. If this is the case #order was probably created either via parameters from a request or with values pulled from the database.
This probably isn't that significant though, both his example and your example work - I think the author was just showing how a call to the OrderConverter would look, and ignored how the Order object got created.
attr_reader :order creates a "getter" method for the #order instance variable in OrderConverter - it's not needed for to_pdf - it would be used to get the Order back out of the OrderConverter via converter.order. I don't see any need to have this in the code you've given so far, but maybe there's some need for it later.
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.
I have a rails model class
class Model < ActiveRecord::Base
has_many :object_collection
def add_object(object)
object_collection.push object // works
#object_collection.push object // does not work
self.object_collection.push object // works
end
end
I was wondering if someone can please explain to me why the # does not work yet self does i thought these two meant the same
cheers
They are not the same. Consider the following Ruby code:
class Person
attr_accessor :employer
end
john = Person.new
john.employer = "ACME"
john.employer # equals "ACME"
The method attr_accessor conveniently generates an attribute reader and writer for you (employer= and employer). You can use these methods to read and write an attribute, which is stored in the instance variable #employer.
Now, we can rewrite the above to the following, which is functionally identical to the code above:
class Person
def employer=(new_employer)
#works_for = new_employer
end
def employer
#works_for
end
end
john = Person.new
john.employer = "ACME"
john.employer # equals "ACME"
Now, the instance variable #employer is no longer used. We chose to write the accessors manually, and have the freedom to pick a different name for the instance variable. In this particular example, the name of the instance variable is different than the name of the attribute accessors. There is nothing that prevents you from doing that.
This is similar to how ActiveRecord stores its attributes internally. They are not stored in instance variables of the same name, that is why your push call to #object_collection does not work.
As you may understand, attribute readers and writers offer a certain abstraction that can hide the implementation details from you. Reading and writing instance variables directly in subclasses is therefore generally considered bad practice.
#foo identifies an instance variable called #foo. foo identified a method called foo.
By default, instance variables in Ruby are private. It means you cannot access the value of an instance variable unless you have some public method that exposes the value.
Those methods are called setters and getters. By convenction, setter and getter have the same name of the instance variable, but this is not a requirement.
class MyClass
def initialize
#foo
end
def foo=(value)
#foo = foo
end
def foo
#foo
end
def an_other_foo=(value)
#foo = foo
end
def an_other_foo
#foo
end
end
Though methods and instance variables can have similar names, thery are different elements.
If this topic is not clear to you, you probably need to stop playing with Rails and go back studying how Ruby works.
In your specific case, object_collection doesn't exist as an instance variable because it's an association method.
They do not mean the same thing. One is an instance variable, the other is a method.
The #foo means "the value of the instance variable foo", where as self.foo means "the value of a call to the method foo on myself".
It is typical for a method foo= to set the #foo instance variable, so I can see how someone new to the language might be confused. I'd encourage you to pick up a book on the ruby language. There's one specifically for people who have done some rails but never learned ruby proper. You often can hack rails without understanding the language or what these statements mean, but you'll be far less productive than someone who spends the small amount of time it takes to learn the ruby language itself.
As a general rule, use the self.foo form whenever you can, as this is less sensitive to changes in the classes definition.