Ruby中块的使用顺序是什么
What is the order of using blocks in Ruby
我正在创建一个 gem 来支持一些来自命令行的邮件。我用了一些Gem。
我正在使用 Mail Gem。正如您在 mail gem
的描述中看到的那样。
mail = Mail.new do
from 'mikel@test.lindsaar.net'
to 'you@test.lindsaar.net'
subject 'This is a test email'
body File.read('body.txt')
end
在块中,我从 Mail
class(从、到、主题、正文)中调用方法。这是有道理的,所以我在自己的邮件程序中构建它 class
def initialize(mail_settings, working_hours)
@mail_settings = mail_settings
@working_hours = working_hours
@mailer = Mail.new do
to mail_settings[:to]
from mail_settings[:from]
subject mail_settings[:subject]
body "Start #{working_hours[:start]} \n\
Ende #{working_hours[:end]}\n\
Pause #{working_hours[:pause]}"
end
end
这看起来很简单。只需调用块并填写我通过构造函数获得的值。 现在 我的问题来了。
我试图将邮件的正文构造放到一个单独的方法中。但是我不能在 gem 的 Mail
构造函数中使用它。
module BossMailer
class Mailer
def initialize(mail_settings, working_hours)
@mail_settings = mail_settings
@working_hours = working_hours
@mailer = Mail.new do
to mail_settings[:to]
from mail_settings[:from]
subject mail_settings[:subject]
body mail_body
end
end
def mail
@mailer.delivery_method :smtp, address: "localhost", port: 1025
@mailer.deliver
end
def mail_body
"Start #{working_hours[:start]} \n\
Ende #{working_hours[:end]}\n\
Pause #{working_hours[:pause]}"
end
end
结束
此代码出现此错误。
这意味着我无法在此块中使用我的 class 方法或 class 变量(以 @a
开头)。
问题
块中的执行顺序是什么?如果我设置我的变量 @mail_settings
,我就不能在块中使用它。 Ruby 是在 Mail
class 中搜索 @mail_settings
吗?为什么我可以通过块使用 BossMailer::Mailer
构造函数中的给定参数而不出现错误?
如果我使用和变量将内容解析到块中,为什么这会起作用? (body_content = mail_body
) 有效!
def initialize(mail_settings, working_hours)
@mail_settings = mail_settings
@working_hours = working_hours
body_content = mail_body
@mailer = Mail.new do
to mail_settings[:to]
from mail_settings[:from]
subject mail_settings[:subject]
body body_content
end
end
一切都与上下文有关。
mail = Mail.new do
from 'mikel@test.lindsaar.net'
to 'you@test.lindsaar.net'
subject 'This is a test email'
body File.read('body.txt')
end
from
、to
方法(以及其余)是 Mail::Message
instance. For you to be able to call them in this nice DSL-manner, the block you pass to constructor is instance_eval'ed.
上的方法
这意味着在此块内部,self
不再是您的邮件程序,而是一封邮件。因此,无法访问您的邮件程序方法。
而不是 instance_eval
,他们可以只有 yield
或 block.call
,但这不会使 DSL 成为可能。
至于为什么局部变量有效:这是因为 ruby 块是词法范围的 closures (意思是,它们保留了声明的局部上下文。如果有从定义块的地方可见的局部变量,它会在调用块时记住变量及其值)
替代方法
不要使用块形式。使用这个:https://github.com/mikel/mail/blob/0f9393bb3ef1344aa76d6dac28db3a4934c65087/lib/mail/message.rb#L92-L96
mail = Mail.new
mail['from'] = 'mikel@test.lindsaar.net'
mail[:to] = 'you@test.lindsaar.net'
mail.subject 'This is a test email'
mail.body = 'This is a body'
代码
尝试 commenting/uncommenting 一些行。
class Mail
def initialize(&block)
# block.call(self) # breaks DSL
instance_eval(&block) # disconnects methods of mailer
end
def to(email)
puts "sending to #{email}"
end
end
class Mailer
def admin_mail
# get_recipient = 'vasya@example.com'
Mail.new do
to get_recipient
end
end
def get_recipient
'sergio@example.com'
end
end
Mailer.new.admin_mail
问题是 mail_body
是在 Mail::Message
的上下文中计算的,而不是在 BossMailer::Mailer
class 的上下文中计算的。考虑以下示例:
class A
def initialize
yield
end
end
class B
def initialize(&block)
instance_eval { block.call }
end
end
class C
def initialize(&block)
instance_eval(&block)
end
end
class Caller
def test
A.new { hi 'a' }
B.new { hi 'b' }
C.new { hi 'c' }
end
def hi(x)
puts "hi there, #{x}"
end
end
Caller.new.test
这会让你
hi there, a
hi there, b
`block in test': undefined method `hi' for #<C:0x286e1c8> (NoMethodError)
查看 gem 的代码,这正是发生的事情:
Mail.new
just passes the block given to Mail::Message
's constructor.
The said constructor works exactly as the C
case above。
instance_eval
基本上改变了当前上下文中 self
的内容。
关于为什么 B
和 C
情况不同 - 你可以认为 &
将 'change' block
对象从 proc 到块(是的,我在那里选择的变量名不是很好)。更多关于区别 here.
我正在创建一个 gem 来支持一些来自命令行的邮件。我用了一些Gem。
我正在使用 Mail Gem。正如您在 mail gem
的描述中看到的那样。
mail = Mail.new do
from 'mikel@test.lindsaar.net'
to 'you@test.lindsaar.net'
subject 'This is a test email'
body File.read('body.txt')
end
在块中,我从 Mail
class(从、到、主题、正文)中调用方法。这是有道理的,所以我在自己的邮件程序中构建它 class
def initialize(mail_settings, working_hours)
@mail_settings = mail_settings
@working_hours = working_hours
@mailer = Mail.new do
to mail_settings[:to]
from mail_settings[:from]
subject mail_settings[:subject]
body "Start #{working_hours[:start]} \n\
Ende #{working_hours[:end]}\n\
Pause #{working_hours[:pause]}"
end
end
这看起来很简单。只需调用块并填写我通过构造函数获得的值。 现在 我的问题来了。
我试图将邮件的正文构造放到一个单独的方法中。但是我不能在 gem 的 Mail
构造函数中使用它。
module BossMailer
class Mailer
def initialize(mail_settings, working_hours)
@mail_settings = mail_settings
@working_hours = working_hours
@mailer = Mail.new do
to mail_settings[:to]
from mail_settings[:from]
subject mail_settings[:subject]
body mail_body
end
end
def mail
@mailer.delivery_method :smtp, address: "localhost", port: 1025
@mailer.deliver
end
def mail_body
"Start #{working_hours[:start]} \n\
Ende #{working_hours[:end]}\n\
Pause #{working_hours[:pause]}"
end
end
结束
此代码出现此错误。
这意味着我无法在此块中使用我的 class 方法或 class 变量(以 @a
开头)。
问题
块中的执行顺序是什么?如果我设置我的变量 @mail_settings
,我就不能在块中使用它。 Ruby 是在 Mail
class 中搜索 @mail_settings
吗?为什么我可以通过块使用 BossMailer::Mailer
构造函数中的给定参数而不出现错误?
如果我使用和变量将内容解析到块中,为什么这会起作用? (body_content = mail_body
) 有效!
def initialize(mail_settings, working_hours)
@mail_settings = mail_settings
@working_hours = working_hours
body_content = mail_body
@mailer = Mail.new do
to mail_settings[:to]
from mail_settings[:from]
subject mail_settings[:subject]
body body_content
end
end
一切都与上下文有关。
mail = Mail.new do
from 'mikel@test.lindsaar.net'
to 'you@test.lindsaar.net'
subject 'This is a test email'
body File.read('body.txt')
end
from
、to
方法(以及其余)是 Mail::Message
instance. For you to be able to call them in this nice DSL-manner, the block you pass to constructor is instance_eval'ed.
这意味着在此块内部,self
不再是您的邮件程序,而是一封邮件。因此,无法访问您的邮件程序方法。
而不是 instance_eval
,他们可以只有 yield
或 block.call
,但这不会使 DSL 成为可能。
至于为什么局部变量有效:这是因为 ruby 块是词法范围的 closures (意思是,它们保留了声明的局部上下文。如果有从定义块的地方可见的局部变量,它会在调用块时记住变量及其值)
替代方法
不要使用块形式。使用这个:https://github.com/mikel/mail/blob/0f9393bb3ef1344aa76d6dac28db3a4934c65087/lib/mail/message.rb#L92-L96
mail = Mail.new
mail['from'] = 'mikel@test.lindsaar.net'
mail[:to] = 'you@test.lindsaar.net'
mail.subject 'This is a test email'
mail.body = 'This is a body'
代码
尝试 commenting/uncommenting 一些行。
class Mail
def initialize(&block)
# block.call(self) # breaks DSL
instance_eval(&block) # disconnects methods of mailer
end
def to(email)
puts "sending to #{email}"
end
end
class Mailer
def admin_mail
# get_recipient = 'vasya@example.com'
Mail.new do
to get_recipient
end
end
def get_recipient
'sergio@example.com'
end
end
Mailer.new.admin_mail
问题是 mail_body
是在 Mail::Message
的上下文中计算的,而不是在 BossMailer::Mailer
class 的上下文中计算的。考虑以下示例:
class A
def initialize
yield
end
end
class B
def initialize(&block)
instance_eval { block.call }
end
end
class C
def initialize(&block)
instance_eval(&block)
end
end
class Caller
def test
A.new { hi 'a' }
B.new { hi 'b' }
C.new { hi 'c' }
end
def hi(x)
puts "hi there, #{x}"
end
end
Caller.new.test
这会让你
hi there, a
hi there, b
`block in test': undefined method `hi' for #<C:0x286e1c8> (NoMethodError)
查看 gem 的代码,这正是发生的事情:
Mail.new
just passes the block given to Mail::Message
's constructor.
The said constructor works exactly as the C
case above。
instance_eval
基本上改变了当前上下文中 self
的内容。
关于为什么 B
和 C
情况不同 - 你可以认为 &
将 'change' block
对象从 proc 到块(是的,我在那里选择的变量名不是很好)。更多关于区别 here.