Can't understand Ruby's magic - ruby-on-rails

In railscasts project you can see this code:
before(:each) do
login_as Factory(:user, :admin => true)
end
The corresponding definition for the function is:
Factory.define :user do |f|
f.sequence(:github_username) { |n| "foo#{n}" }
end
I can't understand how the admin parameter is passing to function, while in the function there's no word about admin parameter. Thanks

Factory.define is not a function definition, it is a method that takes a symbol or string (in this case user) and a block that defines the factory you are making. Factory(:user, :admin => true) makes a User object, with admin attributes. It is not calling the code in your second snippet, it is calling Factory() which initializes a factory, and selects one (in this case the one defined in second snippet). Then it passes options in hash form to Factory as well.
Factory selects the :user factory which is very generic. The option :admin=>true just tells Factory to set the admin instance variable on User to true.
This is actually what it is calling in factory.rb in factory girl
def initialize(name, options = {}) #:nodoc:
assert_valid_options(options)
#name = factory_name_for(name)
#options = options
#attributes = []
end
So Factory(name,options) is equivalent to Factory.new(name,options) in this code.
http://www.ruby-doc.org/core/classes/Kernel.html Notice Array and String etc have similar constructs. I am trying to figure out how they did that now.
This is all confusing even for decent Ruby programmers. I recommend strongly the book "Metaprogramming Ruby" It is probably the best book I have read in ruby and it tells you a lot about this magic stuff.

Michael Papile's response is essentially correct. However, I'd like to elaborate upon it a bit as there are some technical nuances that you might wish to be aware of. I looked over the code for railscasts and factory_girl and I believe there are a few extra pieces to the puzzle that explain how the :admin => true arg ends up creating the admin attribute of the user factory. The attribute addition does not actually happen by way of Factory's initialize() method, although as Michael pointed out that method is indeed being called in service of building the new user factory object.
I'm going to include in this explanation all the steps I took in case you'd like to see how to go about investigating similar questions you might have.
Since your original post is dated Feb. 17th I looked at the version of railscasts that closely matches that date.
I looked in its Gemfile:
https://github.com/ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/Gemfile
Line 18:
gem "factory_girl_rails"
I then checked out the commit of factory_girl_rails that most closely matched the Feb 17th date.
https://github.com/thoughtbot/factory_girl_rails/blob/544868740c3e26d8a5e8337940f9de4990b1cd0b/factory_girl_rails.gemspec
Line 16:
s.add_runtime_dependency('factory_girl', '~> 2.0.0.beta')
factory_girl version 2.0.0.beta was actually not so easy to find. There are no github tags with that name so I just checked out the closest in terms of commit date.
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/syntax/vintage.rb
Lines 122-128:
# Shortcut for Factory.default_strategy.
#
# Example:
# Factory(:user, :name => 'Joe')
def Factory(name, attrs = {})
Factory.default_strategy(name, attrs)
end
So the Factory invocation in railscasts is actually calling a convenience method which invokes the "default strategy", which is located in the same file:
Lines 39-52:
# Executes the default strategy for the given factory. This is usually create,
# but it can be overridden for each factory.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# The result of the default strategy.
def self.default_strategy(name, overrides = {})
self.send(FactoryGirl.find(name).default_strategy, name, overrides)
end
Note that FactoryGirl.find is invoked to get the object on which to invoke default_strategy. The find method resolves to here:
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/registry.rb
Lines 12-14:
def find(name)
#items[name.to_sym] or raise ArgumentError.new("Not registered: #{name.to_s}")
end
Here the name is :user. Thus we wish to invoke default_strategy on the user factory. As Michael Papile pointed out, this user factory was defined and registered by the railscasts code that you originally thought was the class definition for Factory.
https://ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/spec/factories.rb
Lines 23-25:
Factory.define :user do |f|
f.sequence(:github_username) { |n| "foo#{n}" }
end
So in investigating what the default strategy is for the user factory, I looked around in the railscasts project and found this:
https://ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/spec/factories.rb
Lines 43-45:
def default_strategy #:nodoc:
#options[:default_strategy] || :create
end
:create is the default strategy. We go back to factory_girl to find the def for create.
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/syntax/methods.rb
Lines 37-55:
# Generates, saves, and returns an instance from this factory. Attributes can
# be individually overridden by passing in a Hash of attribute => value
# pairs.
#
# Instances are saved using the +save!+ method, so ActiveRecord models will
# raise ActiveRecord::RecordInvalid exceptions for invalid attribute sets.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# A saved instance of the class this factory generates, with generated
# attributes assigned.
def create(name, overrides = {})
FactoryGirl.find(name).run(Proxy::Create, overrides)
end
The create strategy calls the run method defined here:
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/factory.rb
Lines 86-97:
def run(proxy_class, overrides) #:nodoc:
proxy = proxy_class.new(build_class)
overrides = symbolize_keys(overrides)
overrides.each {|attr, val| proxy.set(attr, val) }
passed_keys = overrides.keys.collect {|k| FactoryGirl.aliases_for(k) }.flatten
#attributes.each do |attribute|
unless passed_keys.include?(attribute.name)
attribute.add_to(proxy)
end
end
proxy.result(#to_create_block)
end
A translation/summarization of what this code is doing:
First, the proxy object is built by calling new on the proxy_class, which in this case is Proxy::Create, which is defined here:
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/proxy/create.rb
Basically all you need to know is that proxy is building a new user factory object and invoking callbacks before and after the factory object is created.
Going back to the run method, we see that all the extra args that were originally passed into the Factory convenience method (in this case, :admin => true) are now being labelled as overrides. The proxy object then invokes a set method, passing in each attribute-name/value pair as args.
The set() method is part of the Build class, the parent class of Proxy.
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/proxy/build.rb
Lines 12-14:
def set(attribute, value)
#instance.send(:"#{attribute}=", value)
end
Here #instance refers to the proxied object, the user factory object.
This then, is how :admin => true is set as an attribute on the user factory that the railscasts spec code creates.
If you want, you can google "programming design patterns" and read about the following patterns: Factory, Proxy, Builder, Strategy.
Michael Papile wrote:
http://www.ruby-doc.org/core/classes/Kernel.html Notice Array and
String etc have similar constructs. I am trying to figure out how they
did that now.
If you are still curious, the Array and String you see in the Kernel doc are actually just factory methods used to create new objects of those types. That's why no new method invocation is needed. They are not actually constructor calls per se, but they do allocate and initialize Array and String objects, and hence under the hood they are doing the equivalent of calling initialize() on objects of those types. (In C though, of course, not Ruby)

I don't think that second snippet is the definition for the function. Function definitions have def and end. I think that second snippet looks like a function or method being called with an argument of :user and a block that takes an f parameter.
Of course with metaprogramming you can never really be sure what the hell is going on.

Related

Is it possible to call a previously defined method from within a method of the same name?

Is it possible to overwrite a method and still fallback to the original method (given no superclass is involved)?
def User
def method
# do some original stuff
end
def method
# do some new stuff
call_the_original :method
end
end
Hopefully, my specific example will make my meaning more clear.
Using activestorage has_one_attached :avatar in a User model adds a setter method. I want to do some stuff when this setter is called, but I still want the original method to run.
class User
has_one_attached :avatar
# According to the source (see references) this mixes in the following setter method
def avatar=(attachable)
# do activestorage stuff
end
# I want to add some custom functions to this, before still running "do activestorage
# stuff". I could copy, paste and edit the entire function. But out of interest,
# I wondered if a more elegant solution exists.
def avatar=(attachable)
# do my stuff
super(attachable)
end
end
super obviously does not work because User is not inheriting from anything in which avatar=() is defined.
I could create e.g. MasterUser class containing has_one_attached and from which User inherits, but this seems overkill for just this particular case.
I could submit to a custom_avatar_method=(attachable) which calls avatar=(attachable).
But with this question what I'm really interested in is whether there a way to call a previously defined method from a method of the same name?
References:
#has_one_attached source
You can make use of alias_method to access the previous definition here:
class User
def avatar=(attachable)
# do activestorage stuff
end
alias_method :original_avatar=, :avatar=
def avatar=(attachable)
# do my stuff
self.original_avatar=(attachable)
end
end
Another option is saving the old method inside a variable before defining the new method with the same name. Then call the variable from inside the newly defined method:
class User
def avatar=(attachable)
# do activestorage stuff
end
instance_method(:avatar=).tap do |avatar_eq|
define_method(:avatar=) do |attachable|
# do my stuff
avatar_eq.bind(self).call(attachable)
end
end
end
In the above example define_method(:avatar=) has to be used, since a regular def avatar= wont let you access the avatar_eq variable.
The code is somewhat more complicated than JagdeepSinghs answer, but leaves the class less cluttered with methods. The old method is no longer defined and thus can no longer be called by itself.
References:
Module#instance_method to get the previously defined method
Object#tap to namespace a variable to a small portion of the class definition
Module#define_method to define the new method with the same name
UnboundMethod#bind to bind the unbound method to the current User instance
Method#call to call the bound previously defined method

Overwriting default accessors with options

I am using Ruby on Rails 4 and I would like to know what could be the pitfalls when I overwrite default accessors. The Rails' Official Documentation says (in the initial lines):
The mapping that binds a given Active Record class to a certain
database table will happen automatically in most common cases, but can
be overwritten for the uncommon ones.
More, in that documentation there is the "Overwriting default accessors" section which makes me think that I can do it without any problem. What do you think about?
In my case I would like to overwrite attribute accessors in order to provide some options, something like this:
# Given my Article model has :title and :content attributes
# Then I would like to overwrite accessors providing options this way:
class Article < ActiveRecord::Base
def title(options = {})
# Some logic...
end
def content(options = {})
# Some logic...
end
end
# So that I can run
#article.title # => "Sample Title"
#article.title(:parse => true) # => "Sample *Title*"
#article.content # => "Sample description very long text"
#article.content(:length => :short) # => "Sample description..."
Maybe this is more Ruby than Rails, but will be the #article.title calling the title(options => {}) method or it will call the Rails attribute accessor that access the related database table column value?
Update (after commenting)
Since it seems that in the above code default accessors are not overwritten, is there a way to provide options for those accessors so to reach what I am looking for? If so, how?
#article.title #=> will call your method
#article.title(:parse => true) #=> will call your method
There is no method overloading in ruby if that is what you are looking for.
Looking closer at the official documentation I see where your code diverges.
You forgot "=" when defining your method.
class Foo < ActiveRecord::Base
def self.bar=(value)
#foo = value
return 'OK'
end
end
Foo.bar = 3 #=> 3
WARNING: Never rely on anything that happens inside an assignment method,
(eg. in conditional statements like in the example above)

How to test dynamically defined methods that are based on other model

I have a model called DeviceState which contains states like "active","offline","online".
I have another model called Device which belongs_to to DeviceState.
To have methods such as #device.active? and #device.offline? on the device model, I've defined a dynamic method like so:
DeviceState.all.each do |method|
define_method((method.name + "?").to_sym) do
self.device_state.name == method.name
end
end
My problem is that when I try to create tests on the Device model, the dynamic method are not created because the DeviceState model hasn't been populated on the database while the test environment started up. So by the time I create the DeviceState data with factory girl, it would be too late.
One solution I tried is to seed the DeviceState in spec_helper, however while this worked for the first test it didn't work for all the rest as the database cleaner removed the data.
What would be the best way to overcome those dynamically defined methods?
As everyone pointed out in the comments, you should make the list of states static rather than dynamic based on a database table.
If you do decide to make this list static, you do the following to avoid having to write a new method every time you add a state.
app\models\devise.rb
class Devise < ActiveRecord::Base
...
extend EnumFieldUtil
enum_field :devise_state, %w[active offline online]
...
end
Using a utility method that you can define in lib folder so that you can re-use it in other models if neccesary
lib\enum_field_util.rb
module EnumFieldUtil
def enum_field(field, allowed_values)
allowed_values.each do |value|
class_eval %Q{
define_method("is_#{value}?") { self.#{field} == '#{value}' }
}
end
class_eval %Q{
validates_inclusion_of :#{field}, :in => #{allowed_values}, :message => "{{value}} must be in #{allowed_values.join ','}"
}
end
end
Inspired by Ryan Bate's Rail Casts on state machines

Rails FactoryGirl defining syntax

While using factory girl gem, we create a factories.rb file with the syntax as
FactoryGirl.define do
factory :model do
...
end
...
end
So what exactly does FactoryGirl.define syntax means ?
Is it similar to
class FactoryGirl
def factory :model do
end
end
Thanks
FactoryGirl, like many Ruby gems, defines a "domain specific language" or DSL for the purpose of simplifying configuration. This is a common pattern.
Your example looks like this:
FactoryGirl.define do
factory :model do
...
end
...
end
What's happening is the factory method is being called with the argument :model which is additionally passed a block. As always, the method in question is free to decide what to do with the block. In this case it is saved and executed later during the factory generation process.
Your re-interpretation of it doesn't make any sense as that's not valid Ruby. You cannot have a symbol as an argument specifier. Remember that factory is a pre-existing method, not one you're defining at that point.
If this is all a bit hazy you'll need to experiment with blocks more to see how they work within Ruby. They're used for a number of things so you need to understand how each sets expectations on what the block can do, what it should return, and how many times it will be called, if at all.
In ruby, anything with do end is a block and all blocks are attached to a method.
So in your example, FactoryGirl.define is a method call with a block as a parameter. factory :model is also a method call with a block as a parameter, but in this case the :model is also a parameter passed in.
You can think of the methods being defined in FactoryGirl conceptually like this:
class FactoryGirl
def self.define
yield # do something w/ the block passed in
...
end
def factory(model_name, &block)
block.call # do something w/ the block passed in
...
end
end

Ruby/Rails: class_eval doesn't want to evaluate this code

To generate mocks for Omniauth, I Added this method to config/environments/development.rb
def provides_mocks_for(*providers)
providers.each do |provider|
class_eval %Q{
OmniAuth.config.add_mock(provider, {
:uid => '123456',
:provider => provider,
:nickname => 'nickname',
:info => {
'email' => "#{provider}#webs.com",
'name' => 'full_name_' + provider
}
})
}
end
end
then I call in the same file:
provides_mocks_for :facebook, :twitter, :github, :meetup
But I get:
3.1.3/lib/active_support/core_ext/kernel/singleton_class.rb:11:in `class_eval': can't create instance of singleton class (TypeError)
class_evaland module_eval (which are equivalent) are used to evaluate and immediately execute strings as Ruby code. As such they can be used as a meta-programming facility to dynamically create methods. An example is
class Foo
%w[foo bar].each do |name|
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}
puts '#{name}'
end
RUBY
end
end
It will create two methods foo and bar which print their respective values. As you can see, I create a string containing the actual source code of the function and pass it into class_eval.
While this is a very capable instrument of executing dynamically created code, it must be used with great care. If you make errors here, BAD THINGS WILL HAPPEN™. E.g. if you use user-supplied values when generating code, make really sure the variables contain only values you expect. Eval-based function should generally be used as the last resort.
A cleaner and generally preferred variant is to use define_method like so:
class Foo
%w[foo bar].each do |name|
define_method name do
puts name
end
end
end
(Note that MRI is a wee bit faster with the eval variant. But that doesn't matter most of the time when compared to the added safety and clarity.)
Now in your given code, you effectively write code into a string that can be run directly. Using class_eval here leads to the string being executed in the context of the top-most object (Kernel in this case). As this is a special singleton object which can't be instanciated (similar to nil, true and false), you get this error.
However, as you create directly executable code, you don't need to use class_eval (or any form of eval) at all. Just run your code in a loop as is. And always remember: meta-programming variants (of which the eval methods are among the most bad-ass) should only be used as the last resort.

Resources