"::Chef.Recipe.send(:include, ...)" versus "include ..."?

Hello,

While investigating the setup of some cookbooks, I bumped into this line:

https://github.com/opscode/cookbooks/blob/master/mysql/recipes/server.rb#L20

Can someone explain me why the meta-level invocation is used here
instead of just a direct invocation?

So

::Chef::Recipe.send(:include, Opscode::OpenSSL::Password)

instead of

include Opscode::OpenSSL::Password

Cheers,

Ringo

Ringo,
To avoid this :slight_smile:

[Thu, 26 May 2011 13:32:20 +0000] FATAL: NoMethodError: undefined method `include' for #Chef::Recipe:0xb6c36764

Full stack trace looks like this:

NoMethodError: undefined method include' for #<Chef::Recipe:0xb6c36764> /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/mixin/recipe_definition_dsl_core.rb:56:in method_missing'
/var/chef/cache/cookbooks/mysql/recipes/server.rb:22:in from_file' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/cookbook_version.rb:578:in load_recipe'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/mixin/language_include_recipe.rb:40:in include_recipe' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/mixin/language_include_recipe.rb:27:in each'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/mixin/language_include_recipe.rb:27:in include_recipe' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/run_context.rb:72:in load'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/run_context.rb:69:in each' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/run_context.rb:69:in load'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/client.rb:195:in setup_run_context' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/client.rb:159:in run'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/application/client.rb:239:in run_application' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/application/client.rb:229:in loop'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/application/client.rb:229:in run_application' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/application.rb:66:in run'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/chef-client:26
/usr/bin/chef-client:19:in `load'

But seriously, the main reason is that recipe files are not regular Ruby...that is to say they are a DSL that is evaluated by the chef-client at runtime. Chef uses some of Ruby's method_missing magic to evaluate the recipe DSL (resource definitions) in your recipe code, in this case 'include' cannot be evaluated as it is passed up the method_missing chain.

The variant we use in 'mysql::recipe'

::Chef::Recipe.send(:include, Opscode::OpenSSL::Password)

actually sends the 'include' message to Chef::Recipe class definition with the Opscode::OpenSSL::Password module as a payload. In this case we want to make the method 'secure_password' available to this instance of Chef::Recipe (ie Chef::Recipe:0xb6c36764). So essentially we are in an instance of Chef::Recipe sending a message to it's class definition.

We could have also used 'extend' like so:

extend Opscode::OpenSSL::Password

That would make every method in the Opscode::OpenSSL::Password module available to EVERY instance of Chef::Recipe, but that feels dirty.

We could have accomplished the same thing as using extend by creating our library slightly differently also:

class Chef
class Recipe
def secure_password

RANDOM CODE HERE

end
end
end

This essential would open up the Chef::Recipe class at runtime and add a secure_password method and thus making it available to every instance of Chef::Recipe. I believe this is the example you will see on the wiki in the Libraries section [0].

Hope that helps.

Seth

--
Opscode, Inc.
Seth Chisamore, Senior Technical Evangelist
IRC, Skype, Twitter, Github: schisamo

[0] http://wiki.opscode.com/display/chef/Libraries

On Thursday, May 26, 2011 at 9:27 AM, Ringo De Smet wrote:

Hello,

While investigating the setup of some cookbooks, I bumped into this line:

https://github.com/opscode/cookbooks/blob/master/mysql/recipes/server.rb#L20

Can someone explain me why the meta-level invocation is used here
instead of just a direct invocation?

So

::Chef::Recipe.send(:include, Opscode::OpenSSL::Password)

instead of

include Opscode::OpenSSL::Password

Cheers,

Ringo