Why do migrations need the table block param?
Why does the ruby on rails migration syntax look like this:
create_table :my_table do |t|
t.integer :col
t.integer :col2
t.integer :col3
end
And not:
create_table :my_table do
integer :co开发者_StackOverflowl
integer :col2
integer :col3
end
Personally I find the second snippet much more readable, are there any reasons why the implementation uses the first?
The fundamental implementation of the two approaches is different.
In the first (and actual) case, create_table
calls yield
with a TableDefinition
object. So t
in your example block points to that TableDefinition
. The alternative method is to use instance_eval. This would look something like:
def create_table(name, &block)
table_definition = TableDefinition.new
# Other setup
table_definition.instance_eval(&block)
# More work
end
Which way you do it is partially a matter of preference. However, some people are not fans of eval so they like to avoid this. Also, using the yield
method makes it clearer what object you're working with.
My understanding is that ruby is lexically scoped, meaning that "integer" has to refer to something defined at the point it occurs in the code. You'd need dynamic scoping to accomplish what you're asking for.
It may be that I'm wrong and at least one of procs, blocks and lambdas is dynamically scoped, but then you still have your answer -- obscure details of how scope behaves is not a good thing to expect a programmer to know.
Basically the interface designer should have chosen to do so due to this little trick regarding scopes and how eval
and instance_eval
work, check this example:
Having 2 classes Foo
and Boo
with the following definitions:
class Foo
def speak(phrase)
puts phrase
end
def self.generate(&block)
f = Foo.new
f.instance_eval(&block)
end
end
class Boo
attr_reader :name
def initialize(name) ; @name = name ; end
def express
Foo.generate { speak name}
end
end
Generally this should work fine for most cases, but some situations like the following statement will issue an error:
Boo.new("someone").express #`express': undefined local variable or method `name' for #<Foo:0xb7f582fc> (NameError)
We don't have access here to the instance methods of Boo
inside Foo
instances that's because we are using instance_eval
, so the method name
which is defined for Boo
instances is not in the scope for Foo
instances.
To overcome such problems it’s better to redefine generate as follows:
class Foo
def speak(phrase)
puts phrase
end
def self.generate(&block)
f = Foo.new
block.arity < 1 ? f.instance_eval(&block) : block.call(f)
end
end
This is a flexible interface where you evaluate the code block depending on the passed block params. Now we have to pass the current foo object as a param when we need to call instance methods on it, let's redefine Boo
, check express
and talk
:
class Boo
attr_reader :name
def initialize(name) ; @name = name ; end
def express
Foo.generate { |f| f.speak name}
end
def talk(anything)
Foo.generate { speak anything}
end
end
Boo.new("someone").express #=> someone
Boo.new("someone").talk("whatever") #=> whatever
精彩评论