Почему почтовый блок не может видеть мою переменную?

#ruby #sinatra

#ruby #синатра

Вопрос:

Я новичок в Ruby и задаюсь вопросом, почему я получаю ошибку в этой ситуации, используя драгоценный камень «mail» в простом приложении Sinatra:

 post "/email/send" do

  @recipient = params[:email]

  Mail.deliver do 
    to @recipient # throws error as this is undefined
    from 'server@domain.com'
    subject 'testing sendmail'
    body 'testing sendmail'
  end

  erb :email_sent

end
 

Однако это работает нормально:

 post "/email/send" do

  Mail.deliver do 
    to 'me@domain.com'
    from 'server@domain.com'
    subject 'testing sendmail'
    body 'testing sendmail'
  end

  erb :email_sent

end
 

Я подозреваю, что это как-то связано с областью действия блока и моим непониманием этого.

Комментарии:

1. Вы уверены, что ваша проблема связана с экземпляром var, а не с params[:email] ? Вы пробовали его выводить? Также здесь должно быть достаточно локальной переменной, поскольку блок в любом случае является замыканием.

Ответ №1:

Как говорит Юлик, Mail#delivery выполняет ваш блок с помощью #instance_exec , который просто изменяется self при запуске блока (иначе вы не смогли бы вызывать методы #to и #from внутри блока).

Что вы действительно можете здесь сделать, так это использовать тот факт, что блоки являются замыканиями. Это означает, что он «запоминает» все локальные переменные вокруг него.

 recipient = params[:email]
Mail.deliver do 
    to recipient # 'recipient' is a local variable, not a method, not an instance variable
...
end
 

Опять же, кратко:

  • переменные экземпляра и вызовы методов зависят от self
  • #instance_exec изменяет self ;
  • локальные переменные не зависят от self блоков и запоминаются блоками, потому что блоки являются замыканиями.

Комментарии:

1. И небольшое дополнение: это поведение не зависит от версии Ruby. Поэтому я бы удалил слова «Ruby 1.9» из заголовка вопроса, это может сбить с толку.

2. Итак, моя ошибка заключалась в использовании переменной экземпляра? Я использовал переменную экземпляра, потому что хотел, чтобы эти данные были доступны в шаблоне ERB, и — если я не ошибаюсь — это означает, что это должен быть экземпляр var . Я могу обойти это, либо объявив локальную переменную, либо используя один из подходов, предложенных Железным дровосеком. Спасибо, пока люблю Ruby, но многому нужно научиться 🙂

Ответ №2:

Если вы прочтете документы для Mail дальнейшего, вы найдете хорошее альтернативное решение, которое будет работать. Вместо того, чтобы использовать:

 Mail.deliver do 
  to @recipient # throws error as this is undefined
  from 'server@domain.com'
  subject 'testing sendmail'
  body 'testing sendmail'
end
 

вы можете использовать new() метод Mail, передавая параметры, и игнорировать блок:

 Mail.new(
  to:      @recipient,
  from:    'server@domain.com',
  subject: 'testing sendmail',
  body:    'testing sendmail'
).deliver!
 

или альтернативные определения элементов хэша:

 Mail.new(
  :to      => @recipient,
  :from    => 'server@domain.com',
  :subject => 'testing sendmail',
  :body    => 'testing sendmail'
).deliver!
 

В pry или irb вы увидите:

 pry(main)> Mail.new(
pry(main)* to: 'me@domain.com',
pry(main)* from: 'me@' << `hostname`.strip,
pry(main)* subject: 'test mail gem',
pry(main)* body: 'this is only a test'
pry(main)* ).deliver!
=> #<Mail::Message:59273220, Multipart: false, Headers: <Date: Fri, 28 Oct 2011 09:01:14 -0700>, <From: me@myhost.domain.com>, <To: me@domain.com>, <Message-ID: <4eaad1cab65ce_579b2e8e6c42976d@myhost.domain.com>>, <Subject: test mail gem>, <Mime-Version: 1.0>, <Content-Type: text/plain>, <Content-Transfer-Encoding: 7bit>>
 

У new метода есть несколько вариантов, которые вы можете использовать. Это также из документов и может работать лучше:

В качестве дополнительного примечания вы также можете создать новое электронное письмо, создав объект Mail::Message напрямую, а затем передав значения с помощью строки, символа или прямых вызовов метода. Дополнительную информацию см. в разделе Mail::Message .

  mail = Mail.new
 mail.to = 'mikel@test.lindsaar.net'
 mail[:from] = 'bob@test.lindsaar.net'
 mail['subject'] = 'This is an email'
 mail.body = 'This is the body'
 

далее следует mail.deliver! .

Также обратите внимание, что в предыдущем примере существует несколько способов доступа к различным заголовкам в конверте сообщения. Это гибкий камень, который, кажется, хорошо продуман и хорошо повторяет путь Ruby.

Комментарии:

1. Спасибо, что провели это исследование для меня 🙂 Я слишком внимательно изучал документы GitHub, вместо того, чтобы смотреть на RubyGems. Урок, извлеченный из этого. Еще раз спасибо.

Ответ №3:

Я думаю, это потому, что Mail gem использует instance_exec under the hood. instance_exec использует переменные экземпляра из объекта, к которому он вызывается, а не от вызывающего. Что я бы сделал, так это нашел метод в почтовом геме, который не использует приемы экземпляра, но передает явный объект конфигурации в блок, и действовал оттуда. Избавляет от нескольких седых волос.