Home > Back-end >  Ruby: understanding data structure
Ruby: understanding data structure

Time:05-05

Most of the Factorybot factories are like:

FactoryBot.define do
  factory :product do
    association    :shop
    title          { 'Green t-shirt' }
    price          { 10.10 }
 end
end

It seems that inside the ":product" block we are building a data structure, but it's not the typical hashmap, the "keys" are not declared through symbols and no commas are used.

So my question is: what kind of data structure is this? and how it works?

How declaring "association" inside the block doesn't trigger a:

NameError: undefined local variable or method `association' 

when this would happen on many other situations. Is there a subject in compsci related to this?

CodePudding user response:

The block is not a data structure, it's code. association and friends are all method calls, probably being intercepted by method_missing. Here's an example using that same technique to build a regular hash:

class BlockHash < Hash
  def method_missing(key, value=nil)
    if value.nil?
      return self[key]
    else
      self[key] = value
    end
  end
  def initialize(&block)
    self.instance_eval(&block)
  end
end

With which you can do this:

h = BlockHash.new do 
  foo 'bar'
  baz :zoo
end
h
#=> {:foo=>"bar", :baz=>:zoo}
h.foo
#=> "bar"
h.baz
#=> :zoo

CodePudding user response:

I have not worked with FactoryBot so I'm going to make some assumptions based on other libraries I've worked with. Milage may vary.

The basics: FactoryBot is a class (Obviously) define is a static method in FactoryBot (I'm going to assume I still haven't lost you ;) ). Define takes a block which is pretty standard stuff in ruby. But here's where things get interesting. Typically when a block is executed it has a closure relative to where it was declared. This can be changed in most languages but ruby makes it super easy. instance_eval(block) will do the trick. That means you can have access to methods in the block that weren't available outside the block.

factory on line 2 is just such a method. You didn't declare it, but the block it's running in isn't being executed with a standard scope. Instead your block is being immediately passed to FactoryBot which passes it to a inner class named DSL which instance_evals the block so its own factory method will be run.

line 3-5 don't work that way since you can have an arbitrary name there. ruby has several ways to handle missing methods but the most straightforward is method_missing. method_missing is an overridable hook that any class can define that tells ruby what to do when somebody calls a method that doesn't exist.

Here it's checking to see if it can parse the name as an attribute name and use the parameters or block to define an attribute or declare an association. It sounds more complicated than it is. Typically in this situation I would use define_method, define_singleton_method, instance_variable_set etc... to dynamically create and control the underlying classes. I hope that helps. You don't need to know this to use the library the developers made a domain specific language so people wouldn't have to think about this stuff, but stay curious and keep growing.

  • Related