Nick Sieger: RailsConf: Bill Katz - Metaprogramming Writertopia do what you love tag:blog.nicksieger.com,2005:Typo Typo 2007-08-31T16:41:58+00:00 Nick Sieger urn:uuid:beeab649-29ff-4113-b389-bf4514295d5f 2006-06-24T17:27:00+00:00 2007-08-31T16:41:58+00:00 RailsConf: 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&#8217;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">&quot;</span><span class="string">moderator</span><span class="punct">&quot;</span> <span class="ident">current_user</span><span class="punct">.</span><span class="ident">has_role?</span> <span class="punct">&quot;</span><span class="string">member</span><span class="punct">&quot;,</span> <span class="ident">workshop</span> <span class="ident">workshop</span><span class="punct">.</span><span class="ident">accepts_role?</span> <span class="punct">&quot;</span><span class="string">member</span><span class="punct">&quot;,</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">&lt;</span> <span class="constant">ApplicationController</span> <span class="ident">permit</span> <span class="punct">&quot;</span><span class="string">rubyists and wanna_be_rubyists</span><span class="punct">&quot;,</span> <span class="symbol">:except</span> <span class="punct">=&gt;</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 &#8220;anonymous&#8221;, you don&#8217;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 &#8220;How do we permit?&#8221; 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&#8217;ll have to go over to the writertopia developer link to see it. Bill has said he&#8217;ll post his slides online as well.</p> <h2>Questions</h2> <ul> <li>Use of <code>method_missing</code> &#8211; 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&#8217;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&#8217;s metaclass, then to the object&#8217;s class, then to the superclass of the singleton class, then the class&#8217;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>