I've been browsing the source code for rails, and find a number of mentions to "delegate"
What does this do and how does this work?
here is the official explanation:
delegate(*methods) public
Provides a delegate class method to easily expose contained objects’ public methods as your own.
class Greeter < ActiveRecord::Base
def hello
'hello'
end
def goodbye
'goodbye'
end
end
class Foo < ActiveRecord::Base
belongs_to :greeter
delegate :hello, to: :greeter
end
Foo.new.hello # => "hello"
Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
here's some other explanations of how it works with examples:
http://brettu.com/rails-daily-ruby-tip-20-use-the-delegate-method-in-rails-to-reduce-code/
http://www.simonecarletti.com/blog/2009/12/inside-ruby-on-rails-delegate/
http://pivotallabs.com/rails-delegates-are-even-more-useful-than-i-knew/
Delegates help you stay within the LoD without braking the SRP.
http://en.wikipedia.org/wiki/Law_of_Demeter
Related
I'm trying to solve a strange issue.
I'm extending ActiveRecord using a module.
module StringyAssociationIds
def stringy_ids(association)
define_method("stringy_#{association}_ids=") do |comma_seperated_ids|
self.send("#{association}_ids=", comma_seperated_ids.to_s.split(","))
end
define_method("stringy_#{association}_ids") do
send("#{association}_ids").join(",")
end
end
end
ActiveRecord::Base.extend(StringyAssociationIds)
I have a class "Gate" where I have an association.
class Gate < ActiveRecord::Base
include Productable
stringy_ids :product
end
The association is defined with a join table:
module Productable
extend ActiveSupport::Concern
included do
has_many :productable_products, as: :productable
has_many :products, through: :productable_products
end
end
When I try to create a new Gate I have an error:
undefined method `stringy_ids' for #<Class:0x007f91e12bb7e8>
Where is my fault?
Edit: I try also to add an extension inside the lib directory (autoloaded by application.rb)
module ActiveRecordExtension
extend ActiveSupport::Concern
def stringy_ids(association)
define_method("stringy_#{association}_ids=") do |comma_seperated_ids|
self.send("#{association}_ids=", comma_seperated_ids.to_s.split(","))
end
define_method("stringy_#{association}_ids") do
send("#{association}_ids").join(",")
end
end
end
# include the extension
ActiveRecord::Base.send(:include, ActiveRecordExtension)
I try also in console:
ActiveRecordExtension.instance_methods
=> [:stringy_ids]
So my extension is loaded...
Your class method stringy_ids is defined on ActiveRecord::Base, not Gate. Unlike instance methods, class methods are not inherited because the singleton class of Gate is not a subclass of the singleton class of ActiveRecord::Base.
StringyAssociationIds is not extended.
Actually, ActiveRecord::Base.extend(StringyAssociationIds) does not run. Move this code in config/initializer
I have a lib file lister_extension.rb
module ListerExtension
def lister
puts "#{self.class}"
end
end
And Post model
class Post < ActiveRecord::Base
has_many :reviews
extend ListerExtension
def self.puts_hello
puts "hello123123"
end
end
All is good when I call this in rails c:
2.1.1 :003 > Post.lister
Class
=> nil
But what happens when I want to add a class to my module?
For example:
module ListerExtension
class ready
def lister
puts "#{self.class}"
end
end
end
I get this error
TypeError: wrong argument type Class (expected Module)
When I call Post.first in rails c
From the doc for extend:
Adds to obj the instance methods from each module given as a
parameter.
Hence, you can't access this class through extended class. Have a look into including modules instead of extending them (read about ActionSupport::Concern module as well) or have a go with self.extended method (ref here)
TL;DR , in ruby you can´t extend with classes, you extend/include with modules
regards
updated: example for concern
include / extend with activesupport concern
module Ready
extend ActiveSupport::Concern
# this is an instance method
def lister
....
end
#this are class methods
module ClassMethods
def method_one(params)
....
end
def method_two
....
end
end
end
then in a ActiveRecord Model , like Post
class Post < AR
include Ready
end
with this procedure you will get the instance methods and class methods for free, also you can set some macros like when use a included block,
module Ready
extend ActiveSupport::Concern
included do
has_many :likes
end
end
hope that helps,
regards
I have a function that does this:
def blank_to_negative(value)
value.is_number? ? value : -1
end
If the value passed is not a number, it converts the value to -1.
I mainly created this function for a certain model, but it doesn't seem appropriate to define this function in any certain model because the scope of applications of this function could obviously extend beyond any one particular model. I'll almost certainly need this function in other models, and probably in views.
What's the most "Rails Way" way to define this function and then use it everywhere, especially in models?
I tried to define it in ApplicationHelper, but it didn't work:
class UserSkill < ActiveRecord::Base
include ApplicationHelper
belongs_to :user
belongs_to :skill
def self.splice_levels(current_proficiency_levels, interest_levels)
Skill.all.reject { |skill| !current_proficiency_levels[skill.id.to_s].is_number? and !interest_levels[skill.id.to_s].is_number? }.collect { |skill| {
:skill_id => skill.id,
:current_proficiency_level => blank_to_negative(current_proficiency_levels[skill.id.to_s]),
:interest_level => blank_to_negative(interest_levels[skill.id.to_s]) }}
end
end
That told me
undefined method `blank_to_negative' for #
I've read that you're "never" supposed to do that kind of thing, anyway, so I'm kind of confused.
if you want to have such a helper method in every class in your project, than you are free to add this as a method to Object or whatever you see fits:
module MyApp
module CoreExtensions
module Object
def blank_to_negative
self.is_number? ? self : -1
end
end
end
end
Object.send :include, MyApp::CoreExtensions::Object
There are a few options:
Monkey-patch the method into ActiveRecord and it will be available across all of your models:
class ActiveRecord::Base
def blank_to_negative(value)
value.is_number? ? value : -1
end
end
Add a "concern" module which you then mix into selected models:
# app/concerns/blank_to_negate.rb
module BlankToNegate
def blank_to_negative(value)
value.is_number? ? value : -1
end
end
# app/models/user_skill.rb
class UserSkill < ActiveRecord::Base
include BlankToNegate
# ...
end
Ruby Datatypes functionality can be extended. They are not sealed. Since you wan to use it in all places why not extend FIXNUM functionality and add a method blank_to_negative to it.
Here's what I ended up doing. I put this code in config/initializers/string_extensions.rb.
class String
def is_number?
true if Float(self) rescue false
end
def negative_if_not_numeric
self.is_number? ? self : -1
end
end
Also, I renamed blank_to_negative to negative_if_not_numeric, since some_string.negative_if_not_numeric makes more sense than some_string.blank_to_negative.
I am PHP dev and at the moment I am learning Rails (3) and of course - Ruby. I don't want to believe in magic and so I try to understand as much as I can about things that happen "behind" Rails. What I found interesting are the method calls like has_one or belongs_to in ActiveRecord models.
I tried to reproduce that, and came with naive example:
# has_one_test_1.rb
module Foo
class Base
def self.has_one
puts 'Will it work?'
end
end
end
class Model2 < Foo::Base
has_one
end
Just running this file will output "Will it work?", as I expected.
While searching through rails source I found responsible function: def has_one(association_id, options = {}).
How could this be, because it is obviously an instance (?) and not a class method, it should not work.
After some researching I found an example that could be an answer:
# has_one_test_2.rb
module Foo
module Bar
module Baz
def has_one stuff
puts "I CAN HAS #{stuff}?"
end
end
def self.included mod
mod.extend(Baz)
end
end
class Base
include Bar
end
end
class Model < Foo::Base
has_one 'CHEEZBURGER'
end
Now running has_one_test_2.rb file will output I CAN HAS CHEEZBURGER. If I understood this well - first thing that happens is that Base class tries to include Bar module. On the time of this inclusion the self.included method is invoked, which extends Bar module with Baz module (and its instance has_one method). So in the essence has_one method is included (mixed?) into Base class. But still, I don't fully get it. Object#extend adds the method from module but still, I am not sure how to reproduce this behaviour using extend. So my questions are:
What exactly happened here. I mean, still don't know how has_one method become class method? Which part exactly caused it?
This possibility to make this method calls (which looks like configuration) is really cool. Is there an alternative or simpler way to achieve this?
You can extend and include a module.
extend adds the methods from the module as class methods
A simpler implementation of your example:
module Bar
def has_one stuff
puts "I CAN HAS #{stuff}?"
end
end
class Model
extend Bar
has_one 'CHEEZBURGER'
end
include adds the methods from the module as instance methods
class Model
include Bar
end
Model.new.has_one 'CHEEZBURGER'
Rails uses this to dynamically add methods to your class.
For example you could use define_method:
module Bar
def has_one stuff
define_method(stuff) do
puts "I CAN HAS #{stuff}?"
end
end
end
class Model
extend Bar
has_one 'CHEEZBURGER'
end
Model.new.CHEEZBURGER # => I CAN HAS CHEEZBURGER?
I commend you for refusing to believe in the magic. I highly recommend you get the Metaprogramming Ruby book. I just recently got it and it was triggering epiphanies left and right in mah brainz. It goes over many of these things that people commonly refer to as 'magic'. Once it covers them all, it goes over Active Record as an example to show you that you now understand the topics. Best of all, the book reads very easily: it's very digestible and short.
Yehuda went through some alternatives on way to Rails3: http://yehudakatz.com/2009/11/12/better-ruby-idioms/
Moreover, you can use a (usually heavily abused, but sometimes quite useful) method_missing concept:
class Foo
def method_missing(method, *arg)
# Here you are if was some method that wasn't defined before called to an object
puts "method = #{method.inspect}"
puts "args = #{arg.inspect}"
return nil
end
end
Foo.new.abracadabra(1, 2, 'a')
yields
method = :abracadabra
args = [1, 2, "a"]
Generally, this mechanism is quite often used as
def method_missing(method, *arg)
case method
when :has_one
# implement has_one method
when :has_many
# ...
else
raise NoMethodError.new
end
end
I have a model, Show and a module Utilities
class Show < ActiveRecord::Base
include Utilities
...
def self.something
fix_url("www.google.com")
end
end
My Utilities file is in lib/utilities.rb
module Utilities
def fix_url(u)
!!( u !~ /\A(?:http:\/\/|https:\/\/)/i ) ? "http://#{u}" : u
end
end
But Rails is throwing a NoMethodError for "fix_url" when I call it in my show class. Do I have to do something different when including a module in my model?
Thanks!
try injecting that mixin via the extend instead of include. Basically, because you are calling the mixin method from a class method, but including a mixin only makes its instance methods available. You can use the extend style to get class methods.
Search around for Ruby include and extend to learn the differences. A common pattern is to do it like here:
http://www.dcmanges.com/blog/27
Where you use the included hook to mixin both instance and class level methods.
#Tony - this works for me
class User < ActiveRecord::Base
extend Utilities
def self.test
go()
end
end
module Utilities
def go
puts "hello"
end
end
From console:
>> User.test
hello
=> nil
At no point do I have to explicitly call a method with self.
It worked for me. Have you tried restarting your server/console session?
Edit: If you want to just call Utilities.fix_url you can do that - no include/extend necessary.