Nick Sieger: RailsConf: Bill Katz - Metaprogramming Writertopiado what you lovetag:blog.nicksieger.com,2005:TypoTypo2007-08-31T16:41:58+00:00Nick Siegerurn:uuid:beeab649-29ff-4113-b389-bf4514295d5f2006-06-24T17:27:00+00:002007-08-31T16:41:58+00:00RailsConf: Bill Katz - Metaprogramming Writertopia<p>Bill Katz took a code-focused approach to explaining metaprogramming
and DSLs.</p>
<h2>Resources</h2>
<ul>
<li><a href="http://www.writertopia.com/developers">http://www.writertopia.com/developers</a> has an authorization plugin used
in writertopia, which is the main focus of this talk.</li>
<li><a href="http://www.simbiome.org">http://www.simbiome.org</a> and <a href="https://wimtk.org/svn/simbiome">https://wimtk.org/svn/simbiome</a> contain
code for physics based simulation resources.</li>
</ul>
<p>Bill applies Paul Graham’s quote on bottom-up design and lisp as
justification for using Ruby to program DSLs.</p>
<h2>Authorization plugin</h2>
<p>In search of a better authorization scheme, Bill wanted an elegant
syntax using blocks to delineate sections of code that require
specific permissions, and a more intuitive way of managing the data
surrounding access controls.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="ident">current_user</span><span class="punct">.</span><span class="ident">has_role?</span> <span class="punct">"</span><span class="string">moderator</span><span class="punct">"</span>
<span class="ident">current_user</span><span class="punct">.</span><span class="ident">has_role?</span> <span class="punct">"</span><span class="string">member</span><span class="punct">",</span> <span class="ident">workshop</span>
<span class="ident">workshop</span><span class="punct">.</span><span class="ident">accepts_role?</span> <span class="punct">"</span><span class="string">member</span><span class="punct">",</span> <span class="ident">user</span>
<span class="ident">current_user</span><span class="punct">.</span><span class="ident">is_moderator_of</span> <span class="ident">workshop</span>
<span class="ident">user</span><span class="punct">.</span><span class="ident">is_eligible_for?</span> <span class="ident">campbell_award</span></code></pre></div>
<h2>Implementing <code>permit</code></h2>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">MeetingController</span> <span class="punct"><</span> <span class="constant">ApplicationController</span>
<span class="ident">permit</span> <span class="punct">"</span><span class="string">rubyists and wanna_be_rubyists</span><span class="punct">",</span> <span class="symbol">:except</span> <span class="punct">=></span> <span class="symbol">:public_text</span></code></pre></div>
<p>We need a class method. Where to put it? How does this work? (Insert
pretty diagram of instance, singleton class and superclass here.)
Metaclassees are “anonymous”, you don’t see them by looking at class
ancestors.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="constant">ActionController</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">.</span><span class="ident">send</span><span class="punct">(</span><span class="symbol">:include</span><span class="punct">,</span> <span class="constant">Authorization</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">)</span>
<span class="constant">ActionView</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">.</span><span class="ident">send</span><span class="punct">(</span><span class="symbol">:include</span><span class="punct">,</span> <span class="constant">Authorization</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">::</span><span class="constant">ControllerInstanceMethods</span><span class="punct">)</span></code></pre></div>
<p>Singleton methods from the point of view of the object are the same as
instance methods in the metaclass. See slides on “How do we permit?”
for details. Bill chose to tuck the DSL-related methods into modules
and used <code>self.included</code>, <code>class_eval</code> and other goodies to set
up the methods in the correct places. There is more than way to do
this but I suspect Bill chose to do it for purposes of cleanly
separating concerns along the lines of where in the class hierarchy
the methods live.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">.</span><span class="ident">send</span><span class="punct">(</span><span class="symbol">:include</span><span class="punct">,</span>
<span class="constant">Authorization</span><span class="punct">::</span><span class="constant">ObjectRolesTable</span><span class="punct">::</span><span class="constant">UserExtensions</span><span class="punct">,</span>
<span class="constant">Authorization</span><span class="punct">::</span><span class="constant">ObjectRolesTable</span><span class="punct">::</span><span class="constant">ModelExtensions</span><span class="punct">)</span></code></pre></div>
<p>This loads an <code>acts_as_authorizable</code> plugin which, when used in a
model, inserts an <code>accepts_role</code> family of methods for querying and
setting user roles.</p>
<p>Bill blazed through a whole lot of pretty-looking code here for how to
set up the DSL infrastructure. You’ll have to go over to the
writertopia developer link to see it. Bill has said he’ll post his
slides online as well.</p>
<h2>Questions</h2>
<ul>
<li>Use of <code>method_missing</code> – does that clash with <code>ActiveRecord::Base</code>
usage of <code>method_missing</code>? <em>No, as long as you use <code>super</code> to
invoke the next method in the chain, you’re ok.</em> Ruby module
inclusion semantics handle this for you.</li>
<li>How to insert the class methods? A discussion started around
<code>ActionController::Base.send(:include, ...)</code> and There is More Than
One Way to Do It.</li>
</ul>
<div class="typocode"><pre><code class="typocode_ruby "><span class="constant">ActionController</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">.</span><span class="ident">send</span><span class="punct">(</span><span class="symbol">:include</span><span class="punct">,</span> <span class="constant">Authorization</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">)</span>
<span class="keyword">class </span><span class="class">ActionController::Base</span><span class="punct">;</span> <span class="ident">include</span> <span class="constant">Authorization</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">;</span> <span class="keyword">end</span>
<span class="constant">ActionController</span><span class="punct">::</span><span class="constant">Base</span><span class="punct">.</span><span class="ident">class_eval</span> <span class="punct">{</span> <span class="ident">include</span> <span class="constant">Authorization</span><span class="punct">::</span><span class="constant">Base</span> <span class="punct">}</span></code></pre></div>
<ul>
<li>Question about the class inheritance chain. There are actually two
inheritance chains, one for the classes and one for the metaclasses.
Resolution at method invocation time travels first to the object’s
metaclass, then to the object’s class, then to the superclass of the
singleton class, then the class’s superclass, etc. Blech, that
makes no sense. Instead, go read <a href="http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html">Seeing Metaclasses Early</a>.</li>
</ul>