Yehuda Katz is a member of the Ember.js, Ruby on Rails and jQuery Core Teams; his 9-to-5 home is at the startup he founded, Tilde Inc.. There he works on Skylight, the smart profiler for Rails, and does Ember.js consulting. He is best known for his open source work, which also includes Thor and Handlebars. He travels the world doing open source evangelism and web standards work.

Archive for the ‘Ruby’ Category

Merb Vulnerability Fix (1.0.12)

Over the weekend, it was discovered that the json_pure gem is subject to a DoS attack when parsing specially formed JSON objects. This vulnerability does not affect the json gem, which uses a C extension, or ActiveSupport::JSON, which is used in Rails.

By default, Merb uses the json gem, which is not vulnerable, but falls back to json_pure if json is not available. As a result, if you have json_pure but not json on your system, you may be vulnerable. Additionally, Ruby 1.9.1 (but not Ruby 1.9 trunk) ships with json_pure, which remains vulnerable.

The easiest way to immunize yourself from this problem, no matter what Ruby version you are on, is to upgrade to the latest version of json_pure, which resolves the vulnerability. Additionally, Merb 1.0.12 has been released, which monkey-patches json_pure to remove the vulnerability, but prints a warning encouraging you to upgrade to the latest. Merb 1.0.12 only adds this patch on top of 1.0.11, so it should be perfectly safe to upgrade.

Guest Blogging

I’ll occasionally be posting on the Engine Yard blog in addition to my posts here. Union Station has really stepped up lately in terms of technical content. We’ve had posts on pairing, TDD with Cucumber, Rack and a slew of other things, and I know there’s more in the pipe.

I posted today on how we’ve been refactoring Rails, and how you can apply what we’ve learned to your own projects: let me know what you think!

On Rails Testing

One of the things that has both pleasantly surprised and frustrated me over the past six months is the state of Rails’ internal tests. While the tests can sometimes cover the most obscure, important bugs, they can sometimes be heavily mock-based or very coupled to internal implementation.

In large part, this is because of the requirement that patches come with tests, so the test suite has accumulated a lot of knowledge over time, but isn’t always the clearest when it comes time for a major refactor. Just to be clear, without the test suite, I don’t think the work we’ve been doing would have been possible, so I’m not complaining much.

However, I recently became aware that Sam Ruby, one of the authors of Agile Web Development on Rails, has released a test suite that tests each step in the depot application, as well as each of the advanced chapters.

Last week, Carl and I started digging into the Rails initializer, and the tests in the initializer (railties) are more mock-based and less reliable than the tests in ActionPack (which we’ve been working with so far). They’re pretty reasonable unit tests for individual components, but getting all of the tests to pass did not result in an (even close) bootable Rails app. Thanks to Sam’s tests, we were able to work through getting a booting Rails app by the end of last week.

Over the past week or two, Carl and I have been running the tests a few times a day, and while they definitely take a while to get through, they’ve added a new sense of confidence to the work we’re doing.

Rails Edge Architecture

I’ve talked a bunch about the Rails 3 architecture in various talks, and it’s finally come together enough to do a full blog post on it. With that said, there’s a few things to keep in mind.

We’ve done a lot of work on ActionController, but have only mildly refactored ActionView. We’re going to tackle that next, but I don’t expect nearly as many internal changes as there were for ActionController. Also, we’ve been working on the ActionController changes for a few months, and have focused quite a bit on maintaining backward compatibility (for the user-facing API) with Rails 2.3. If you try edge and find that we’ve broken something, definitely let us know.

AbstractController::Base

One of the biggest internal changes is creating a base superclass that is separated from the notions of HTTP. This AbstractController handles the basic notion of controllers, actions, and action dispatching, and not much else.

At that level of abstraction, there are also a number of modules that can be added in, such as Renderer, Layouts, and Callbacks. The API in the AbstractController is very normalized, and it’s not intended to be used directly by end users.

For instance, the template renderer takes an ActionView::Template object and renders it. Developers working with Rails shouldn’t have to know about Template objects; this is simply an internal API to make it easier to work with the Rails internals and build things on top of Rails.

Another example is related to action dispatching. At its core, action dispatching is simply taking an action name, calling the method if it exists, or raising an exception if it doesn’t. However, other parts of Rails have different semantics. For instance, method_missing is used if the action name is not a public method, and the action is dispatched if no action is present but a template with the same name is present.

In order to make it easy to implement these features at different levels of abstraction, AbstractController::Base calls method_for_action to determine what method to call for an action. By default, that method simply checks to see if the action is present. In ActionController::HideActions, that method is overridden to return nil if actions are explicitly removed using hide_action. ActionController::Base further overrides it to return “default_render”, which handles the rendering.

That’s, in a nutshell, how the new architecture works: simple abstractions that represent a bunch of functionality that you can layer on if you need it.

ActionController::Http

ActionController::Http layers a Rack abstraction on top of the core controller abstraction so that actions can be called as Rack endpoints. On top of the core rendering functionality available in AbstractController::Base, ActionController provides rendering functionality that builds on top of HTTP constructs. For instance, the ActionController renderer knows how to set the content type based on the template it rendered. Additionally, ActionController implements more developer-friendly APIs. Instead of having to know about the ActionView::Template object, developers can just use the name of the template (pretty much the Rails 2.3 rendering API).

ActionController normalizes the developer’s inputs into the inputs that AbstractController is expecting, before calling super (ActionController::Http inherits from AbstractController::Base). Additional functionality is added via the ActionController::Rails2Compatibility module, which provides support for things like stripping a leading “/” off of template names, and ActionController::Base, which provides a final layer of normalization (for instance, normalizing render :action => “foo” and render “foo”). As a result, ActionController::Base ends up being identical to the fully-featured ActionController::Base that you used in Rails 2.3, and none of your app code needs to change.

As an example, let’s look at rendering. If you call render :template => “hello”, the first thing that happens is the normalization pass on ActionController::Base. Since we used a relatively normalized form, not much happens, and the options hash is passed up into the compatibility module, which checks to see if there’s a leading “/” in the template name. Since there isn’t, it passes the options hash up again into the AbstractController::Renderer module.

There, Renderer checks to see if we’ve already set the response body. If we have, a DoubleRenderError is raised. If not, render_to_body is called with the options hash. The first place in the chain we find render_to_body is in ActionController::Renderer, where it processes options like :content_type or :status.

It then calls super, passing control back into AbstractController, which promptly calls _determine_template, again with the options. The job of _determine_template is to take in some options and return the template to render. This hook point is provided so that other modules, like the Layouts module, can use the template that was looked up to control something they care about. In this case, the Layouts module wants to limit the search for a layout to the formats and locale of the template that was actually rendered.

This solves a long-standing problem in Rails where templates and layouts were looked up separately, so it was possible for an HTML layout to be implicitly wrapped around an XML template. No longer :)

The Layouts module gets called first with _determine_template. It calls super, allowing the default template lookup to occur, which populates options[:_template]. It then uses options[:_template] to look up the layout to use, populating options[:_layout]. I hadn’t mentioned it before, but AbstractController uses options with leading underscores, leaving undecorated options for subclasses like ActionController::Base.

After the template to render is determined, Renderer calls _render_template, which makes the call into ActionView.

I know it sounds rather complicated, but a graphic showing the relationships is forthcoming, which should clean things up. The nice thing is that there are several layers of abstraction, and while the final system is complex (mostly because the functionality is complex), it’s reasonably easy to understand the higher levels of abstraction on their own. It’s also easy to put various kinds of normalization into standard places, so the code you’re reading at any given point is code that expects normalized inputs. Since the normalization that Rails does can sometimes be quite gnarly (in the service of making the user experience really pleasant), separating that stuff out can reduce the amount of gnarliness in the internals themselves.

Finally, an important part of an architecture like this is making sure that there is great internal documentation (which we’ve already started with), and some visualizations that show what’s going on. If you were forced to track down the control flow above on your own for the first time, it would probably be non-trivial. So a key part of this architecture is making sure you never have to do that. I would also note that I’m not particularly good at expressing code reading in blog posts, so the process definitely sounds a lot more complex than it actually is ;)

The Importance of Executable Class Bodies

I spent the past few days at JavaOne, where I gave a well-received talk on Ruby, and got to attend a number of sessions on both Ruby and other related technologies.

Out of curiosity, I went to a session on Groovy, a language that has a syntax that is derived directly from Java, but with semantics that are fairly close to Ruby’s. Groovy is missing a number of features that Ruby has, and is more clunky in a number of cases.

For instance, while Ruby has pure open classes, Groovy allows you to open or reopen the metaclass of a class and insert new methods. Groovy 1.6 (released in February) added the ability to insert a number of methods to a metaclass at once.

But what I want to discuss here is another distinction. Unlike Ruby, Groovy does not allow executable code anywhere. Instead Groovy classes are compiled, so runtime code execution inside of class bodies can not work. This means that a large number of the features that make Rails stand out, like declarative callbacks and validations, various forms of accessors, runtime method generation based on introspecting the database, and other per-class mutable structures cannot be implemented nearly as elegantly in Groovy.

In case you’re not familiar, Ruby doesn’t need annotations, because class bodies in Ruby are simply executable code, with a self that is the class that is being defined.

Let’s take a simple example. In Ruby, accessors are defined as followed:

class Car
  attr_accessor :model, :make
end

In Groovy, accessors are defined as:

class Car {
  model
  make
}

At first glance, they seem pretty similar. In both cases, getters and setters are added, and new fields (in the respective languages) exist. The difference is that while Groovy needed to add new syntax to support this, Ruby’s version can be implemented in Ruby itself:

class Class
  def attr_accessor(*names)
    names.each do |name|
      class_eval "def #{name}() @#{name} end"
      class_eval "def #{name}=(val) @#{name} = val end"      
    end
  end
end

Rubinius, a complete implementation of Ruby in Ruby, implements attr_accessor as:

class Class
  def attr_reader(name)
    meth = Rubinius::AccessVariable.get_ivar name
    @method_table[name] = meth
    return nil
  end
 
  def attr_writer(name)
    meth = Rubinius::AccessVariable.set_ivar name
    @method_table["#{name}=".to_sym] = meth
    return nil
  end
 
  def attr_accessor(name)
    attr_reader(name)
    attr_writer(name)
    return true
  end
end

Here, Rubinius exposes the method table to Ruby, and we store a method representing the instance variable directly into the method table. Obviously, this requires more Ruby infrastructure, but it shows how powerful “everything is executable code” can be.

In an effort to support Ruby’s declarative style, Groovy has added what they call “AST Transformations”, which allows a declarative rule plus some code to be converted, at compile time, into different code to be passed into the compiler.

To make this immediately useful, they shipped a bunch of these annotations with Groovy 1.6, so we can take a look at how this is supposed to work. One example is their “Lazy” annotation, which allows the creation of an accessor that is initialized to something slow, so you want to defer initializing the accessor until it is actually accessed. It works like this (from the Groovy documentation):

class Person {
  @Lazy pets = ['Cat', 'Dog', 'Bird']
}

Assuming that creating that Array was slow, this would defer loading the Array until pets was accessed. Pretty nice. Unfortunately, implementing this nice abstraction is a non-trivial operation:

package org.codehaus.groovy.transform;
 
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.*;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.runtime.MetaClassHelper;
import org.codehaus.groovy.syntax.Token;
import org.objectweb.asm.Opcodes;
 
/**
 * Handles generation of code for the @Lazy annotation
 *
 * @author Alex Tkachman
 */
@GroovyASTTransformation(phase= CompilePhase.CANONICALIZATION)
public class LazyASTTransformation implements ASTTransformation, Opcodes {
 
    public void visit(ASTNode[] nodes, SourceUnit source) {
        if (!(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
            throw new RuntimeException("Internal error: wrong types: $node.class / $parent.class");
        }
 
        AnnotatedNode parent = (AnnotatedNode) nodes[1];
        AnnotationNode node = (AnnotationNode) nodes[0];
 
        if (parent instanceof FieldNode) {
            FieldNode fieldNode = (FieldNode) parent;
            final Expression init = getInitExpr(fieldNode);
 
            fieldNode.rename("$" + fieldNode.getName());
            fieldNode.setModifiers(ACC_PRIVATE | (fieldNode.getModifiers() & (~(ACC_PUBLIC|ACC_PROTECTED))));
 
            create(fieldNode, init);
        }
    }
 
    private void create(FieldNode fieldNode, final Expression initExpr) {
        BlockStatement body = new BlockStatement();
        final FieldExpression fieldExpr = new FieldExpression(fieldNode);
        if ((fieldNode.getModifiers() & ACC_VOLATILE) == 0) {
            body.addStatement(new IfStatement(
                    new BooleanExpression(new BinaryExpression(fieldExpr, Token.newSymbol("!=",-1,-1), ConstantExpression.NULL)),
                    new ExpressionStatement(fieldExpr),
                    new ExpressionStatement(new BinaryExpression(fieldExpr, Token.newSymbol("=",-1,-1), initExpr))
            ));
        }
        else {
            body.addStatement(new IfStatement(
                new BooleanExpression(new BinaryExpression(fieldExpr, Token.newSymbol("!=",-1,-1), ConstantExpression.NULL)),
                new ReturnStatement(fieldExpr),
                new SynchronizedStatement(
                        VariableExpression.THIS_EXPRESSION,
                        new IfStatement(
                                new BooleanExpression(new BinaryExpression(fieldExpr, Token.newSymbol("!=",-1,-1), ConstantExpression.NULL)),
                                    new ReturnStatement(fieldExpr),
                                    new ReturnStatement(new BinaryExpression(fieldExpr,Token.newSymbol("=",-1,-1), initExpr))
                        )
                )
            ));
        }
        final String name = "get" + MetaClassHelper.capitalize(fieldNode.getName().substring(1));
        fieldNode.getDeclaringClass().addMethod(name, ACC_PUBLIC, fieldNode.getType(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, body);
    }
 
    private Expression getInitExpr(FieldNode fieldNode) {
        Expression initExpr = fieldNode.getInitialValueExpression();
        fieldNode.setInitialValueExpression(null);
 
        if (initExpr == null)
          initExpr = new ConstructorCallExpression(fieldNode.getType(), new ArgumentListExpression());
 
        return initExpr;
    }
}

Pretty ugly. If you look closely at the code, you’ll see that the amount of code necessary to express even simple concepts is huge, because you’re manipulating an actual AST.

A similar feature in Ruby might look like:

class Person
  lazy(:pets) { ["Cat", "Dog", "Bird"] }
end

And the implementation:

class Class
  def lazy(name, &block)
    define_method("_lazy_#{name}", &block)
    class_eval "def #{name}() @#{name} ||= _lazy_#{name} end"
  end
end

Because the lazy method is just a method being run on class at runtime, we can evaluate code live into the context. First, we define a method on the object called _lazy_pets. Next, we define a method called pets that memoizes the results of calling that method into an instance variable. And that’s it.

A slightly slower solution in Ruby that doesn’t require eval is:

class Class
  def lazy(name)
    ivar = "@#{name}"
    define_method(name) do
      instance_variable_get(ivar) || instance_variable_set(ivar, yield)
    end
  end
end

In this case, since we’re defining the method in a block, we still have access to the block that was passed in to the original lazy method, so we can yield to it inside the new method. Pretty cool, no?

Because all code is executable in Ruby, it’s easy to abstract away repetitive code in around the same number of lines as it took to write the code in the first place. With these simple examples, it would be possible to implement a simpler way to express these transforms. But as these sorts of things are expected to compose well with each other, the flexibility of executable, runtime code starts to really add up, in the same way that languages that are dynamic at runtime can be more flexible and powerful than languages that try to precompute everything at compile time.

Rails Dispatch

It’s been a busy week or two. I just got back from acts_as_conference, which was a blast. I got to talk about “The Merge”; the talk was 50% prepared slides and 50% Q&A. I’m glad I did it that way, since the questions were (as usual) great. DHH did Q&A in the morning of the first day, and fielded some questions about the merge as well. He also took questions on a bunch of other topics (“Do you Test All the Fucking Time?” “No.”).

On the Rails front, I continue to wrestle with callbacks. On the bright side, once it’s all done, there will be a single, uniform callbacks interface for every callback use in the Rails codebase. That includes ActiveRecord callbacks, Dispatch callbacks, Test::Unit callbacks, and of course, before/after/around filters. Once I’m ready, all of the above will have around filters, the biggest obvious change, and they’ll also all be much faster in the face of a number of callbacks.

I’ve completed integrating the new callbacks into Test::Unit and ActionController::Dispatcher, and am in the process of merging them into AP. However, I’m going to take a break for a few days from it and see about extracting AbstractController (I’m stuck in a rut with the integration and need to take a step back for a bit).

On another topic, we had a discussion today about how to handle http parameters when there are multiple parameters with the same name (but without “[]“). I should have a patch to handle this later tonight. Stay tuned!

Status Update — A Fresh Look at Callbacks

After I finished the first stage of the ActionView refactor I was working on (I’m currently working on some small issues with Josh and getting ready to merge it into the official Rails3 branch), I decided to take a fresh look at the performance numbers. As expected, by initial refactor did not really improve performance (it wasn’t intended to — I mainly just moved things around and cleaned up the code path), but one thing that stuck out at me was how expensive the callback system is.

This is the case in both Rails and Merb, although Merb’s smaller callback feature set makes it somewhat faster than Rails’ at present (but not significantly so; both systems use the same basic approach). So I decided to spend some time today trying to make the callbacks faster. The first thing I noticed was that the ActiveSupport callbacks are fairly limited (for instance, missing around filters), and that ActionPack uses a small part of the ActiveSupport system and then layers on scads of additional functionality.

After spending a few hours trying to improve the performance of the callbacks, I remembered that Carl had considered an interesting approach for the Merb callbacks that involved dispensing with iteration in favor of a single compiled method that inlined the filters. In his original experiments (which supported before, after, and around filters, but not conditions), he was able to make filters run about an order of magnitude faster than Merb’s system.

Effectively, the approach boils down to:

before_filter :foo
after_filter :bar
around_filter :baz

def _run_hookz
  foo
  baz do
    yield
    baz
  end
end 

This completely removes the need for iteration at runtime, and compiles down the hooks into a single, speedy method. What complicated matters a bit was:

  • Rails’ support for conditions (also supported in Merb, but not in Carl’s original prototype)
  • Rails’ support for various filter types, including strings (which get evalled), procs, blocks, and arbitrary objects

After playing around a bit, I managed to support conditional filters as well as all the supported Rails filter types using the speedier callback system.I compared the ActionPack filtering system to the old ActiveSupport callbacks to the new callbacks. According to my benchmarks, the new system is 15-20x faster than the old one. Here are the actual benchmarks for 100,000 iterations (2 before filters, 1 after filter with a proc conditions).

      JRuby Results |
---------------------
actionpack    2.406 |
old           1.798 |
new           0.089 |
  MRI (1.8) Results |
---------------------
actionpack    4.190 |
old           3.063 |
new           0.276 |
  MRI (1.9) Results |
---------------------
actionpack    2.617 |
old           2.137 |
new           0.157 |

Keep in mind that I haven’t retrofitted the ActionPack filters to use the new callback system yet; I had to make ActiveSupport::Callbacks support around_filters first, but that there shouldn’t be any reason it won’t work. Also, adding around_filter support to ActiveSupport::Callbacks means that Test::Unit will now have around filters for free as part of the retrofit (this should affect ActiveRecord as well, for anyone who needs around filters in AR’s hooks).

Also, and this is a big caveat: this is most certainly not an optimization that is likely to significantly help most apps where the overhead is in database access or rendering. However, one of my personal goals for the work I’m doing is to reduce the need for things like Rack Metal by reducing the overhead of rendering a request through the full stack. Filters are used throughout the render path and we shockingly using 10-20% of the 4ms a render :text => “Hello world” was taking up. I was surprised that the action itself was only about 7% of the default render path.

So again, shaving off under 1ms is not likely to help many apps, but it is a step in the direction of making it possible to build high-performance APIs and service tiers on top of the full Rails request cycle. You can follow along this callback work on my optimize_callbacks branch.

P.S. Merb 1.0.8 should be released tomorrow. It’ll include a bunch of pending fixes (including a fix for PIDs being overwritten in production when using merb -i)

Another Dispatch: Step 1 of 2 Complete!

A couple of notes of interest for the work over the past few days:

  • Git is holding up surprisingly well. I’ve done daily merges from rails/rails/master into wycats/rails/master and then into wycats/rails/action_view, and have so far only run into a single conflict, which was easy to resolve. Moving Rails to git made the work we’re doing possible!
  • As far as I can tell, all current cases of using render() as an API now use _render_for_parts(). This is a huge milestone. Now that the four-element tuple is the standard across the board, we can push back the path lookup to the callers and unify behind something like _render_for_template(). There’s still quite a bit of work to do in general (most notably around partials and layouts), but things are coming along quite nicely.
  • I was purposely holding off keeping ActionMailer in sync as I did the refactor, because things were too much in flux for that to really be viable. As it turns out, it was relatively simple to move ActionMailer over to the new approach after everything was done. The only real irritation is the repeated use of the render() options to do mostly the same thing. I’ve moved the code that takes a partial path and converts it into the requisite paths into action_pack/common.rb so it can be used in both ActionController and ActionView. I’m not sure if this will be the final resting place but this refactor definitely pulls out some AC/AV common functionality.
  • I am more convinced than ever that a common AbstractController superclass for both ActionController and ActionMailer is the way to go. This was actually the thing that originally brought me to Merb (that firm conviction), and I’m pretty convinced it’s the right approach for Rails as well. Of course, there’s still quite a bit of work to do before it’s possible to really evaluate it, so stay tuned.
  • Lori has been working hard on the ORM adapter for Rails. Effectively, the adapter will work like: ActiveORM.for(@sequel_obj). If the object is compatible with the ActiveORM API (note that the names et al are still very much in flux), it will be passed through. For instance, ActiveORM.for(@ar_obj) will just return the ActiveRecord object. ActiveORM.for(@sequel_obj), however, will return a wrapper around the object that is compatible with Rails expectations. Example: ActiveORM.for(@sequel_obj).new_record?. Note that this paragraph is speculative and represents work that is still very much in flux.

I’m really looking forward to finishing the work that _render_for_parts was a placeholder for. As I go through the codebase, I’m definitely noticing some obvious perf opportunities (non-trivial perf that should show up very clearly on macro-benches) and I’m looking forward to finishing the initial refactor so I can get into some perf optimizations. I should have some initial numbers within a week or so.

Today’s Dispatch: Weaning ActionView off of content negotiation

I’ve been on “vacation” for the past few days and haven’t had a ton of time to work on continuing refactoring, but I did manage to make a bit more progress, and I figured I’d share.

In Rails 2.2, ActionView had a fair number of content negotiation responsibilities. In particular, ActionView::Base had a method called template_format that looked like this:

def template_format
  if defined? @template_format
    @template_format
  elsif controller && controller.respond_to?(:request)
    @template_format = controller.request.format.to_sym
  else
    @template_format = :html
  end
end

Effectively, ActionView had a small content-negotiation responsibility, that effectively entailed grabbing the first acceptable format and using that. Thankfully, this particular piece of code was usually supplanted by Rails’ respond_to code, which performed more proper content-negotiation, and set the template format directly: @response.template.template_format = mime_type.to_sym.

A number of other places, including the JavaScript helpers, also set the template format directly, which meant that for the most part, the template_format method was just a placeholder that was set by other parts of the system. However, there were more than a few cases where the default template_format method was getting used. Instead of having ActionView call back into the request object, I wanted to modify ActionView’s initialization so that ActionController could feed it the list of acceptable formats directly.

With some work, I was able to make the modification, weaning ActionView off of content negotiation. Interestingly, this is related to my overall refactoring, which currently uses a four-element tuple to represent templates (path, acceptable extensions, prefix, and a partial flag). Now that ActionView uses a list of formats internally, it can easily pass them into find_by_parts, which handles figuring out which template to use based on the ordered list of extensions.

Just a note: find_by_parts is almost certainly a temporary creation to facilitate refactoring. However, for the purposes of refactoring, it is a convenient way to make sure that all pieces of the puzzle are passing around compatible values. In the end, we will probably be passing around Template objects, and this intermediate refactoring step will help us get all of our ducks in a row so that we can get there.

A fun diff that sort of demonstrates where I’m going with this is:

   def _pick_partial_template(partial_path) #:nodoc:
-    if partial_path.include?('/')
-      path = File.join(File.dirname(partial_path), "_#{File.basename(partial_path)}")
-    elsif controller
-      path = "#{controller.class.controller_path}/_#{partial_path}"
-    else
-      path = "_#{partial_path}"
+    unless partial_path.include?('/')
+      prefix = (controller && controller.class.controller_path)
     end
-
-    self.view_paths.find_template(path, self.template_format)
+    view_paths.find_by_parts(partial_path, formats, prefix, true)
   end

As you can see, by normalizing all path lookup to the four-element tuple, which includes merging path prefixes and appending “_” to partials, I was able to remove almost all of the code of this method, now requiring only that the controller_path is specified as a prefix if no existing prefix is supplied as part of the path (that’s so render :partial => “foo” inside of the TestController looks for “test/_foo”).

Again, I probably won’t be able to do a ton of work over the next few days, but I’ll blog when I can. Happy holidays folks!

Rails Refactor Update (and Merb 1.0.7.1)

Over the past few days, I’ve been working on refactoring ActionController and ActionView to clean up the interactions between them. You can follow along on my github fork. Some principles I’m following:

  • ActionController and ActionView should work well standalone
  • All request-related information should be calculated in ActionController and passed through into ActionView
  • ActionView should be responsible for figuring out what path to render; ActionController should pass enough information for ActionView to figure it out.
  • Based on the previous, the information passed from ActionController to ActionView will likely be: template_name, list of acceptable extensions (i.e. [:html, :xml]), prefix (usually the controller name), and partial (a boolean indicating whether the template is a partial). This is not nailed down yet, but it has so far served well.
  • So far, I have unified render(:template), render(:file), and render(:action) to use this new conduit, and will be working on partials tomorrow. Partials are quite complicated so my current plan may have to change slightly when I tackle them.
  • Interesting info: ActionPack has a lot of methods that call each other (somewhat circularly), so it wasn’t really possible to just replace the existing conduit in a straight-forward manner. Instead, I created the new method (currently called find_by_parts, which finds a template based on the components I discussed above) and slowly (very slowly) moved existing callers of find_by_path over to use find_by_parts. Thankfully, the Rails test suite caught the initial errors I made, a huge saving grace of the entire effort.
  • A nice side-effect of moving to a single, clean API between AC and AV is that the final code should be easier to understand. Once I’m further along, I’ll post some details of how exactly the interaction works.

We also released Merb 1.0.7.1 today to fix a few issues that were outstanding as well as a 1.0.7 development mode regression. (In general, we will be using the x.x.x.x moniker for midweek releases and x.x.x for weekly releases). The issues that were fixed:

  • Templates should reload again in development mode
  • An issue in the bundler where gems in your local repository were getting greedily installed is fixed
  • An issue that was preventing bin/thor merb:gem:install use is fixed
  • The error message when gems could not be found was slightly improved

To upgrade:

  • gem install merb (make sure merb-gen 1.0.7.1 is installed)
  • in your app, rm -rf tasks/merb.thor
  • in your app, run merb-gen thor
  • you should receive a prompt asking you to override bin/common.rb. Accept the prompt.
  • you’re done

Happy holidays folks!

Archives

Categories

Meta