I'm learning ruby and can't for the life of it figure out what this does:
def topic_list
topics.map(&:name).join(",")
end
def topic_list=(names)
if names.present?
topics = names.split(",")
self.topics = topics.map do |n|
unless topic = Topic.find_by(slug: n)
topic = Topic.create!(name: n)
end
topic
end
end
end
Why would two functions have the same name? Does the first function call the second one?
topic_list is a getter and topic_list= is a setter method. No they are not the same methods.
This question Trying to learn / understand Ruby setter and getter methods will be helpful as a basic food for this concept. So Read it.
The line self.topics = topics.map... in the method topic_list=, and topics.map(&:name).join(",") line in the method topic_list smells there is one getter called topics and setter topics=. I am sure(If you tell this code works altough) about this by looking at your code.
To clarify Arup Rakshit's answer a little more:
Ruby allows having some "weird" characters in its methods, that you would normally not see in other languages. The most notably ones are ?, ! and =.
So, you can define the methods:
def answered?()
not #answer.nil?
end
def answer!()
#answer = Answer.new
end
def answer()
#answer
end
def answer=(answer)
#answer = answer
end
For someone coming from, say, PHP, this looks strange. But really they are all different methods! I have added brackets everywhere, to clarify my point, but in Ruby, you can leave them off, so when a method does not take arguments,people generally leave them off.
def answer()
end
def answer
end
Are the same.
The last two, the answer() and answer=(), are often refered to as getters and setters. The first is often used to test something binary, like has_question? or valid?. The second is referred to as a bang method, answer!, more often used in methods that modify itself, like some_array.merge!(other_array), which modifies some_array.
Related
Sorry if this is too simple. I'm looking for a way to make my ruby code dry : I want to call a number of methods on the same instance variable #var = Model.new(param) :
#var.method1
#var.method2
#var.method3
...
Is it possible to use the send method to write one line of code ? Btw, is it possible to call a block on Model.new to produce some more concise code ?
I believe that DRY should be used to make your code more maintainable, and more readable. I don't think it should be used to shorten the number of characters you type, or show-off your code acrobatics.
Both #Arup's and #p11y's solutions are great, within a context, but as a general rule (before knowing anything about your class or methods), I believe that writing
#var.method1
#var.method2
#var.method3
is more readable and maintainable than writing either
%i[method1 method2 method3].each(&#var.method(:send))
(you need to be fluent in advanced ruby to understand this)
or
#var.method1
.method2
.method3
(again the vanishing act is more confusing to the future reader than helpful)
Always think about who will read your code in 6 months, and what will be the clearest way for him to understand what's happening.
If you build method1, method2, etc. such that they return the instance itself using self, you can build a chainable interface. For example:
class Foo
def method1
# do something
self
end
def method2
# do something
self
end
def method3
# do something
self
end
# more methods...
end
#var = Foo.new
#var.method1.method2.method3
# or if this gets too long
#var.method1
.method2
.method3
Do as below :
%i[method1 method2 method3].each { |m| #var.send(m) }
If you want to make it more short,use :
%i[method1 method2 method3].each(&#var.method(:send))
When I wrote my original answer, I missed the last sentence in your question:
Btw, is it possible to call a block on Model.new to produce some more concise code ?
And the answer to this question is YES. This pattern is a builder pattern, which is implemented in several gems in ruby (such as tire).
The pattern states that the initialize method receives a block, which is run in the context of the created object, using instance_eval. If the block receives a parameter, the instance object is passed to it instead of changing the block's scope:
class Model
def initialize(name, &block)
#name = name
block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given?
end
def method1
# something
end
def method2
# something
end
def method3
# something
end
end
And its usage will be something either like this:
#var = Model.new('model') do
method1
method2
method3
end
or, alternatively:
#var = Model.new('model') do |m|
m.method1
m.method2
m.method3
end
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.
I'd like to override the setter for an association, but write_attribute() isn't working - probably because that method only works for database columns.
I have tried super(), but that doesn't work either (didn't think it would... but it was worth a guess).
How do I override the setter? Here is what I am trying to do:
def parent=(value)
# this line needs to be changed
write_attribute(:parent, value)
if value.subject.start_with?('Re:')
self.subject = "#{value.subject}"
else
self.subject = "Re: #{value.subject}"
end
self.receivers << value.sender
end
What worked for me is the following:
def parent=(new_parent)
# do stuff before setting the new parent...
association(:parent).writer(new_parent)
end
I found one way to do it, but I am disturbed by it:
alias_method :old_parent=, :parent=
def parent=(value)
self.old_parent = value
if value.subject.start_with?('Re:')
self.subject = "#{value.subject}"
else
self.subject = "Re: #{value.subject}"
end
self.receivers << value.sender
end
One thing I don't necessarily like about Rails is that whenever you want to do something that is out of the norm just a bit - but not unreasonable by any means - the "how" is very different than what your intuition would come up with.
It's not a problem when you know the exceptions, but when you're learning, this sort of irregularity and inconsistency on how to do things makes it harder to learn - not easier.
Java might be initially harder to learn, but it's way more consistent. Your intuition can take you a lot further once you think in Java. This is not true once you think in Rails. Rails is about memorization of methods to call and memorization on how to do things. In java, you can reason it out a lot more... and intellisense fills in the rest.
I'm just disappointed. This is a reoccurring pattern for me - I want do something that is just "a little more complex" than the framework examples... and the "how" is inconsistent and takes 30 minutes or maybe even hours to locate and find the answer for it.
In Rails 4.2.1 doc:
# Association methods are generated in a module that is included into the model class,
# which allows you to easily override with your own methods and call the original
# generated method with +super+. For example:
#
# class Car < ActiveRecord::Base
# belongs_to :owner
# belongs_to :old_owner
# def owner=(new_owner)
# self.old_owner = self.owner
# super
# end
# end
Instead of
def parent=(value)
write_attribute(:parent, value)
end
Couldn't you just do:
def parent=(parent)
parent_id = parent.id
end
I found myself writing the following piece of code over and over again:
MyModel.find(my_id).my_field
I wonder if there is a simpler method to write this ?
If you are asking if there is more concise way of writing that.. not sure there is with the standard finders. What you have is pretty small. Just for fun I wrote this for you though :)
class ActiveRecord::Base
def self.method_missing_with_retrieve_just_a_field(method_called, *args, &block)
if(method_called.to_s=~/get_/)
self.find(args[0]).send(method_called.to_s.gsub("get_", ""))
else
method_missing_without_retrieve_just_a_field(method_called, *args, &block)
end
end
class << self
alias_method_chain :method_missing, :retrieve_just_a_field
end
end
If you put this in your config/initializers as some file like crazy_finder.rb you can then just say:
MyModel.get_my_field(my_id)
It doesnt save you much, but just thought it would be fun to write.
In addition to Jake's global solution for every model and every attribute, you can easily define explicit individual accessors:
class MyModel
def self.my_field(id)
find(id).my_field
end
end
Or an array of fields:
class MyModel
class << self
[:my_field, :other_field].each do |field|
define_method field do |id|
find(id).send(field)
end
end
end
end
Are you doing this for same resource over and over again or to many different resources? It would really help if you'd give us better example of what you're trying to do, if you're doing that many times, it would help to give us example of what you're doing many times.
If you're doing this for one resource only to get different values:
If you're doing this in same action over and over again, you're doing it wrong. First store your result in a variable like this:
#mymodel = MyModel.find(id)
and then you can use
#mymodel.my_field
and then again (without the need to find it again)
#mymodel.different_field
or if you want to do this for a collection you can do:
#mymodels = MyModel.find([my_first_id, my_second_id, my_third_id])
etc.. better example from your side would help to help you!
I'm developing an online store, and the customer needs the ability to delete an order and have its products automatically restocked (e.g., for test orders). Here's my first try at implementing this:
class Order < ActiveRecord::Base
def destroy_and_restock
restock_products
destroy
end
protected
def restock_products
line_items.each do |li|
li.product.quantity_on_hand += li.quantity
li.product.save
end
end
end
But what if I need to create another destroy_and_x method later? Why not allow that X to be passed as a parameter to the destroy() method? So now I'm thinking of going with this:
alias :old_destroy :destroy
def destroy(options = {})
if options['restock'] == true
restock_products
end
old_destroy
end
protected
def restock_products
line_items.each do |li|
li.product.quantity_on_hand += li.quantity
li.product.save
end
This is more extensible, but makes me feel somewhat dirty. Am I wrong to feel dirty? Is there a better way of doing this?
I'd say "yes, this is dirty." Your intention isn't to modify the behavior of the 'destroy' method, but rather to do some domain-specific work, then run destroy. Your first approach is great -- define a method that does what you want, and invoke destroy as needed. I think that 'wrapping' or 'monkey-patching' a method, as you're considering, is a technique that's best applied when standard OO approaches can't be used -- eg, when you need to modify/augment behavior in a class that is defined and used outside of your realm of control.
Even if you are considering modifying the behavior of the destroy method itself, I'd suggest overriding the method here, rather than wrapping it:
def destroy(options = {})
restock_products if options['restock']
super() # I think parens are necessary here, to avoid passing options up the chain
end
How about just use a block? Then you dont have to pull your hair apart while designing this in the class and you can do more as and when you need to:
def destroy_after &block
yield if block
destroy
end
Then call it like this:
order.destroy_after { order.restock_products }
I can not think of a good name for this function... but I hope you get the idea.
Horace, I misunderstood your question. I think you are looking for this:
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Now you can keep your method protected and add as many before_destroy things as you like. Hope this works for you without overriding destroy.
Best of luck.
If monkey patching doesn't let you sleep at night, you can achieve the same thing by subclassing. When I'm in the need of a quick hack, or a quick debug hack, I monkey patch as well.