BUY THIS BOOK
Add to Cart

Print Book $34.99


Add to Cart

PDF $27.99

Safari Books Online

What is this?

Add to UK Cart

Print Book £21.99

What is this?

Looking to Reprint or License this content?


Advanced Rails
Advanced Rails

By Brad Ediger
Book Price: $34.99 USD
£21.99 GBP
PDF Price: $27.99

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Foundational Techniques
Simplicity is prerequisite for reliability.
—Edsger W. Dijkstra
Since its initial release in July 2004, the Ruby on Rails web framework has been steadily growing in popularity. Rails has been converting PHP, Java, and .NET developers to a simpler way: a model-view-controller (MVC) architecture, sensible defaults ("convention over configuration"), and the powerful Ruby programming language.
Rails had somewhat of a bad reputation for a lack of documentation during its first year or two. This gap has since been filled by the thousands of developers who use, contribute to, and write about Ruby on Rails, as well as by the Rails Documentation project (http://railsdocumentation.org/). There are hundreds of blogs that offer tutorials and advice for Rails development.
This book's goal is to collect and distill the best practices and knowledge embodied by the community of Rails developers and present everything in an easy-to-understand, compact format for experienced programmers. In addition, I seek to present facets of web development that are often undertreated or dismissed by the Rails community.
Rails brought metaprogramming to the masses. Although it was certainly not the first application to use Ruby's extensive facilities for introspection, it is probably the most popular. To understand Rails, we must first examine the parts of Ruby that make Rails possible. This chapter lays the foundation for the techniques discussed in the remainder of this book.
Metaprogramming is a programming technique in which code writes other code or introspects upon itself. The prefix meta-(from Greek) refers to abstraction; code that uses metaprogramming techniques works at two levels of abstraction simultaneously.
Metaprogramming is used in many languages, but it is most popular in dynamic languages because they typically have more runtime capabilities for manipulating code as data. Though reflection is available in more static languages such as C# and Java, it is not nearly as transparent as in the more dynamic languages such as Ruby because the code and data are on two separate levels at runtime.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
What Is Metaprogramming?
Rails brought metaprogramming to the masses. Although it was certainly not the first application to use Ruby's extensive facilities for introspection, it is probably the most popular. To understand Rails, we must first examine the parts of Ruby that make Rails possible. This chapter lays the foundation for the techniques discussed in the remainder of this book.
Metaprogramming is a programming technique in which code writes other code or introspects upon itself. The prefix meta-(from Greek) refers to abstraction; code that uses metaprogramming techniques works at two levels of abstraction simultaneously.
Metaprogramming is used in many languages, but it is most popular in dynamic languages because they typically have more runtime capabilities for manipulating code as data. Though reflection is available in more static languages such as C# and Java, it is not nearly as transparent as in the more dynamic languages such as Ruby because the code and data are on two separate levels at runtime.
Introspection is typically done on one of two levels. Syntactic introspection is the lowest level of introspection—direct examination of the program text or token stream. Template-based and macro based metaprogramming usually operate at the syntactic level.
Lisp encourages this style of metaprogramming by using S-expressions (essentially a direct translation of the program's abstract syntax tree) for both code and data. Metaprogramming in Lisp heavily involves macros, which are essentially templates for code. This offers the advantage of working on one level; code and data are both represented in the same way, and the only thing that distinguishes code from data is whether it is evaluated. However, there are some drawbacks to metaprogramming at the syntactic level. Variable capture and inadvertent multiple evaluation are direct consequences of having code on two levels of abstraction in the source evaluated in the same namespace. Although there are standard Lisp idioms for dealing with these problems, they represent more things the Lisp programmer must learn and think about.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Ruby Foundations
This book relies heavily on a firm understanding of Ruby. This section will explain some aspects of Ruby that are often confusing or misunderstood. Some of this may be familiar, but these are important concepts that form the basis for the metaprogramming techniques covered later in this chapter.
Classes and modules are the foundation of object-oriented programming in Ruby. Classes facilitate encapsulation and separation of concerns. Modules can be used as mixins—bundles of functionality that are added onto a class to add behaviors in lieu of multiple inheritance. Modules are also used to separate classes into namespaces.
In Ruby, every class name is a constant. This is why Ruby requires class names to begin with an uppercase letter. The constant evaluates to the class object, which is an object of the class Class. This is distinct from the Class object, which represents the actual class Class. When we refer to a "class object" (with a lowercase C), we mean any object that represents a class (including Class itself). When we refer to the "Class object" (uppercase C), we mean the class Class, which is the superclass of all class objects.
The class Class inherits from Module; every class is also a module. However, there is an important distinction. Classes cannot be mixed in to other classes, and classes cannot extend objects; only modules can.
Method lookup in Ruby can be very confusing, but it is quite regular. The easiest way to understand complicated situations is to visualize the data structures that Ruby creates behind the scenes.
Every Ruby object has a set of fields in memory:
klass
A pointer to the class object of this object. (It is klass instead of class because the latter is a reserved word in C++ and Ruby; if it were called class, Ruby would compile with a C compiler but not with a C++ compiler. This deliberate misspelling is used everywhere in Ruby.)
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Metaprogramming Techniques
Now that we've covered the fundamentals of Ruby, we can examine some of the common metaprogramming techniques that are used in Rails.
Although we write examples in Ruby, most of these techniques are applicable to any dynamic programming language. In fact, many of Ruby's metaprogramming idioms are shamelessly stolen from either Lisp, Smalltalk, or Perl.
Often we want to create an interface whose methods vary depending on some piece of runtime data. The most prominent example of this in Rails is ActiveRecord's attribute accessor methods. Method calls on an ActiveRecord object (like person.name) are translated at runtime to attribute accesses. At the class-method level, ActiveRecord offers extreme flexibility: Person.find_all_by_user_id_and_active(42, true) is translated into the appropriate SQL query, raising the standard NoMethodError exception should those attributes not exist.
The magic behind this is Ruby's method_missing method. When a nonexistent method is called on an object, Ruby first checks that object's class for a method_missing method before raising a NoMethodError. method_missing's first argument is the name of the method called; the remainder of the arguments correspond to the arguments passed to the method. Any block passed to the method is passed through to method_missing. So, a complete method signature is:
	def method_missing(method_id, *args, &block)
	  ...
	end
There are several drawbacks to using method_missing:
  • It is slower than conventional method lookup. Simple tests indicate that method dispatch with method_missing is at least two to three times as expensive in time as conventional dispatch.
  • Since the methods being called never actually exist—they are just intercepted at the last step of the method lookup process—they cannot be documented or introspected as conventional methods can.
  • Because all dynamic methods must go through the method_missing method, the body of that method can become quite large if there are many different aspects of the code that need to add methods dynamically.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Functional Programming
The paradigm of functional programming focuses on values rather than the side effects of evaluation. In contrast to imperative programming, the functional style deals with the values of expressions in a mathematical sense. Function application and composition are first-class concepts, and mutable state (although it obviously exists at a low level) is abstracted away from the programmer.
This is a somewhat confusing concept, and it is often unfamiliar even to experienced programmers. The best parallels are drawn from mathematics, from which functional programming is derived.
Consider the mathematical equation x = 3. The equals sign in that expression indicates equivalence: "x is equal to 3." On the contrary, the Ruby statement x = 3 is of a completely different nature. That equals sign denotes assignment: "assign 3 to x." In a functional programming language, equals usually denotes equality rather than assignment. The key difference here is that functional programming languages specify what is to be calculated; imperative programming languages tend to specify how to calculate it.
The cornerstone of functional programming, of course, is functions. The primary way that the functional paradigm influences mainstream Ruby programming is in the use of higher-order functions (also called first-class functions, though these two terms are not strictly equivalent). Higher-order functions are functions that operate on other functions. Higher-order functions usually either take one or more functions as an argument or return a function.
Ruby supports functions as mostly first-class objects; they can be created, manipulated, passed, returned, and called. Anonymous functions are represented as Proc objects, created with Proc.new or Kernel#lambda:
	add = lambda{|a,b| a + b}
	add.class # => Proc
	add.arity # => 2

	# call a Proc with Proc#call
	add.call(1,2) # => 3

	# alternate syntax
	add[1,2] # => 3
The most common use for blocks in Ruby is in conjunction with iterators. Many programmers who come to Ruby from other, more imperative-style languages start out writing code like this:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Examples
This example ties together several of the techniques we have seen in this chapter. We return to the Person example, where we want to time several expensive methods:
	class Person
	  def refresh
	    # ... 
	  end

	  def dup 
	    # ... 
	  end
	end
In order to deploy this to a production environment, we may not want to leave our timing code in place all of the time because of overhead. However, we probably want to have the option to enable it when debugging. We will develop code that allows us to add and remove features (in this case, timing code) at runtime without touching the original source.
First, we set up methods wrapping each of our expensive methods with timing commands. As usual, we do this by monkeypatching the timing methods into Person from another file to separate the timing code from the actual model logic: .
	class Person
	  TIMED_METHODS = [:refresh, :dup]
	  TIMED_METHODS.each do |method|
	    # set up _without_timing alias of original method
	    alias_method :"#{method}_without_timing", method

	    # set up _with_timing method that wraps the original in timing code
	    define_method :"#{method}_with_timing" do
	      start_time = Time.now.to_f
	      returning(self.send(:"#{method}_without_timing")) do
	        end_time = Time.now.to_f

	        puts "#{method}: #{"%.3f" % (end_time-start_time)} s."
	      end
	    end
	  end
	end
We add singleton methods to Person to enable or disable tracing:
	class << Person
	  def start_trace
	    TIMED_METHODS.each do |method|
	      alias_method method, :"#{method}_with_timing"
	    end
	  end

	  def end_trace
	    TIMED_METHODS.each do |method|
	      alias_method method, :"#{method}_without_timing"
	    end
	  end
	end
To enable tracing, we wrap each method call in the timed method call. To disable it, we simply point the method call back to the original method (which is now only accessible by its _without_timing alias).
To use these additions, we simply call the Person.trace method:
	p = Person.new
	p.refresh # => (...)

	Person.start_trace
	p.refresh # => (...)
	# -> refresh: 0.500 s.

	Person.end_trace
	p.refresh # => (...)
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Further Reading
Minero AOKI's Ruby Hacking Guide is an excellent introduction to Ruby's internals. It is being translated into English at http://rhg.rubyforge.org/.
Eigenclass (http://eigenclass.org/) has several more technical articles on Ruby.
Evil.rb is a library for accessing the internals of Ruby objects. It can change objects' internal state, traverse and examine the klass and super pointers, change an object's class, and cause general mayhem. Use with caution. It is available at http:// rubyforge.org/projects/evil/. Mauricio Fernández gives a taste of Evil at http://eigenclass. org/hiki.rb?evil.rb+dl+and+unfreeze.
Jamis Buck has a very detailed exploration of the Rails routing code, as well as several other difficult parts of Rails, at http://weblog.jamisbuck.org/under-the-hood.
One of the easiest-to-understand, most well-architectured pieces of Ruby software I have seen is Capistrano 2, also developed by Jamis Buck. Not only does Capistrano have a very clean API, it is extremely well built from the bottom up. If you haven't been under Capistrano's hood, it will be well worth your time. The source is available via Subversion from http://svn.rubyonrails.org/rails/tools/capistrano/.
Mark Jason Dominus's book Higher-Order Perl (Morgan Kaufmann Publishers) was revolutionary in introducing functional programming concepts into Perl. When Higher-Order Perl was released in 2005, Perl was a language not typically known for its functional programming support. Most of the examples in the book can be translated fairly readily into Ruby; this is a good exercise if you are familiar with Perl. James Edward Gray II has written up his version in his "Higher-Order Ruby" series, at http://blog.grayproductions.net/categories/higherorder_ruby.
The Ruby Programming Language, by David Flanagan and Yukihiro Matsumoto (O'Reilly), is a book covering both Ruby 1.8 and 1.9. It is due out in January 2008. The book includes a section on functional programming techniques in Ruby.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: ActiveSupport and RailTies
[Programs] must be written for people to read, and only incidentally for machines to execute.
—H. Abelson and G. Sussmann
Structure and Interpretation of Computer Programs, MIT Press, 1985
We continue in our bottom-up view of Rails by examining the pieces that form the basis for Rails. ActiveSupport is a library that provides generic, reusable functions that are not specific to any one part of Rails. We can use many of these methods ourselves when writing our application code. RailTies is the other half, containing parts that glue Rails together in a Rails-specific way. Although we will not usually use RailTies functions in our own code, it is important and instructive to examine them.
Most of this chapter is nonsequential; feel free to skip around. However, in accordance with our bottom-up approach to Rails, later chapters will build on this material.
It is very easy to overlook some of Ruby's more useful methods. The best way to find them is to read code. Here are some of the more obscure, but helpful, ones.
  • Array#* an operate as Array#join (if given a string or stringlike argument); it also does repetition:
    	[1, 2, 3] * "; " # => "1; 2; 3"
    
    	[0] * 5 # => [0, 0, 0, 0, 0]
    
  • Array#pack and String#unpack are useful for working with binary files. why the lucky stiff uses Array#pack to stuff a series of numbers into a BMP-formatted sparkline graph without any heavy image libraries, in 13 lines of code (http://redhanded.hobix.com/inspect/sparklinesForMinimalists.html).
  • 	Dir.[] is shorthand for Dir.glob:
    	    Dir["/s*"] # => ["/scripts", "/srv", "/selinux", "/sys", "/sbin"]
    
  • Enumerable#all? returns true if the given block returns a true value for all items in the enumerable. Similarly, Enumerable#any? returns true if the block returns a
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Ruby You May Have Missed
It is very easy to overlook some of Ruby's more useful methods. The best way to find them is to read code. Here are some of the more obscure, but helpful, ones.
  • Array#* an operate as Array#join (if given a string or stringlike argument); it also does repetition:
    	[1, 2, 3] * "; " # => "1; 2; 3"
    
    	[0] * 5 # => [0, 0, 0, 0, 0]
    
  • Array#pack and String#unpack are useful for working with binary files. why the lucky stiff uses Array#pack to stuff a series of numbers into a BMP-formatted sparkline graph without any heavy image libraries, in 13 lines of code (http://redhanded.hobix.com/inspect/sparklinesForMinimalists.html).
  • 	Dir.[] is shorthand for Dir.glob:
    	    Dir["/s*"] # => ["/scripts", "/srv", "/selinux", "/sys", "/sbin"]
    
  • Enumerable#all? returns true if the given block returns a true value for all items in the enumerable. Similarly, Enumerable#any? returns true if the block returns a true value for any item.
    	(1..10).all?{|i| i > 0 && i < 15} # => true
    
    	(1..10).any?{|i| i*i == 9} # => true
    	(1..10).any?{|i| i*i == 8} # => false
    
  • Enumerable#grep filters an enumerable against another object using ===, affording all of the usual flexibility of the === method:
    	[1, 2, 3].methods.grep(/^so/) # => ["sort!", "sort", "sort_by"]
    
    	[1, :two, "three", 4].grep(Fixnum) # => [1, 4]
    
  • Enumerable#sort_by sorts the enumerable by the value of the given block, by performing a Schwartzian transform on the data. It builds up a set of input elements, each stored with the result of applying the block to that element. Because the block should return the same value when called with the same input, it only needs to be called once per input. Thus, O(n) calculations are done rather than O(n lg n).
    However, the sort_by
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
How to Read Code
As implied by the quote introducing this chapter, the primary purpose of source code should not be expressing implementation to a computer; it should be expressing meaning to people. Programming languages are an incredibly expressive and terse medium for the concepts programmers talk about. Proposals to make programming languages more English-like inevitably fail not because of poor implementation but because there is an inherent impedance mismatch between the domains of English language and computer programming.
Thus, computer programming languages should be compared not by their levels of raw power (any Turing-complete language trivially satisfies this requirement) or speed of execution (for most applications, speed is not critical) but by their programmer efficiency—the speed at which a programmer can accurately translate his thoughts into code.
Closely related to programmer efficiency is maintainer efficiency: the ability of a maintainer (who maybe the original developer, 12 months later) to read the code and deduce what is going on. Perl is often criticized for being "write-only"; it is easy to write code that is nearly unreadable to future developers. Such code would have high programmer efficiency at the cost of maintainer efficiency.
Ruby wins on both fronts: most Rubycode is easy to write and read, once you know the basic syntax and semantics. Still, diving into any large project such as Rails is difficult. Here, we discuss ways to begin reading a codebase.
One disadvantage of the dynamic nature of Ruby is that there is little opportunity for development-time reflection on Ruby code. When using a more static language, IDEs can infer the type of variables, and from that deduce the methods available to those variables. Thus, they can offer assistance in coding by suggesting variable and method names. In Ruby, in principle the only way to know the methods available to an object is to evaluate the expression returning that object's value. This is clearly impractical due to side effects of evaluation or differences in development and execution environment. The effect is that it is impossible to write a general code-completion-style IDE for Ruby.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
ActiveSupport
ActiveSupport is the libraryof utilitymethods that Rails uses. We examine them in detail here for two reasons. First, theycan be useful to our application code—we can directlyuse manyof these libraries and methods to our advantage when writing Rails applications. Secondly, we can learn many things about Ruby programming by dissecting these parts. They are small and relatively easy to digest.
dependencies.rb
Dependencies autoloads missing constants by trying to find the file associated with the constant. When you attempt to access a nonexistent constant, such as Message, Dependencies will try to find and load message.rb from any directory in Dependencies.load_paths.
Dependencies defines Module#const_missing and Class#const_missing, which both proxy to Dependencies.load_missing_constant(const_parent, const_id). That method searches the load paths for a file with the appropriate name; if found, Dependencies loads the file and ensures that it defined the appropriate constant.
Alternatively, Rails will create an empty module to satisfy nesting in the case of nested models and controllers. If a directorynamed app/models/store/ exists, Store will be created as an empty module, by the following process:
  1. Some piece of code references the undefined constant Store.
  2. Ruby calls const_missing.
  3. const_missing calls Dependencies.load_missing_constant(Object,:Store).
  4. load_missing_constant attempts to find and load store.rb somewhere in its list of load paths (Dependencies.load_paths). It fails to find such a file.
  5. load_missing_constant sees that app/models/store exists and is a directory. It creates a module, assigns it to the appropriate constant, and returns.
deprecation.rb
The ActiveSupport::Deprecation module provides a method bywhich old APIs are marked for removal. At its core, it is just a fancywarning mechanism. When old APIs are used, they generate a warning in development or test mode. Deprecation warnings are invoked through the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Core Extensions
The Core Extensions are ActiveSupport's collection of extensions to Ruby's core classes and modules. They are basic design patterns solving problems that are encountered often in Ruby. These methods are one level below the Rails API; they are the internal functions that Rails uses. However, we describe them here because they are extremely useful during the process of building a Rails application. The core extensions are low-level utility methods for Ruby; they do not make the impossible possible, but they do help to simplify application code.

Conversions

core_ext/array/conversions.rb
  • Array#to_sentence joins the array's elements and converts to a string:
    	%w(Larry Curly Moe).to_sentence # => "Larry, Curly, and Moe"
    
  • Array#to_s(:db) collects an arrayof ActiveRecord objects (or other objects that respond to the id method) into a SQL-friendly string.
  • Array#to_xml converts an arrayof ActiveRecord objects into XML. This is usually used to implement REST-style web services. It relies on the contained objects' implementation of to_xml (such as ActiveRecord::XmlSerialization.to_xml).
    	render :xml => Product.find(:all).to_xml 
    
  • Note that render(:xml => …) and render(:json => …) are new synonyms for render(:text => …) that change the response's MIME type appropriately.

Grouping

core_ext/array/grouping.rb
  • Array#in_groups_of(size, fill_with) groups elements of an array into fixed-size groups:
    	(1..8).to_a.in_groups_of(3) # => [[1, 2, 3], [4, 5, 6], [7, 8, nil]]
    	(1..8).to_a.in_groups_of(3, 0) # => [[1, 2, 3], [4, 5, 6], [7, 8, 0]] 
    	(1..8).to_a.in_groups_of(3, false) # => [[1, 2, 3], [4, 5, 6], [7, 8]]
    
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
RailTies
RailTies is the set of components that wire together ActiveRecord, ActionController, and ActionView to form Rails. We will examine the two most important parts of RailTies: how Rails is initialized and how requests are processed.
The Rails::Configuration class, defined in initializer.rb, holds the configuration attributes that control Rails. It has several general Rails attributes defined as attributes on the Configuration class, but there is a little cleverness in the framework class stubs. The five class stubs (action_controller, action_mailer, action_view, active_resource, and active_record) act as proxies to the class attributes of their respective Base classes. In this way, the configuration statement:
	config.action_controller.perform_caching = true
is the same as:
	ActionController::Base.perform_caching = true
except with a unified configuration syntax.
initializer.rb
Rails::Initializer is the main class that handles setting up the Rails environment within Ruby. Initialization is kicked off by config/environment.rb, which contains the block:
	Rails::Initializer.run do |config|
	  # (configuration)
	end
Rails::Initializer.run yields a new Rails::Configuration object to the block. Then run creates a new Rails::Initializer object and calls its process method, which takes the following steps in order to initialize Rails:
  1. check_ruby_version: Ensures that Ruby1.8.2 or above (but not 1.8.3) is being used.
  2. set_load_path: Adds the framework paths (RailTies, ActionPack, ActiveSupport, ActiveRecord, Action Mailer, and Action Web Service) and the application's load paths to the Ruby load path. The framework is loaded from vendor/rails or a location specified in RAILS_FRAMEWORK_ROOT.
  3. require_frameworks: Loads each framework listed in the frameworks configuration option. If the framework path was not specified in
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Further Reading
Diomidis Spinellis's book Code Reading: The Open Source Perspective (Addison-Wesley) offers advice on how to approach large codebases, particularly those of open source software.
The Ruby Facets core library is another collection of code that aims to provide utility methods for Ruby. This library covers some of the same ground as the Core Extensions, but also provides additional extensions.
If you need more complicated manipulations to the English language than the Inflector class allows, look to the Ruby Linguistics project.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Rails Plugins
Civilization advances by extending the number of important operations which we can perform without thinking of them.
—Alfred North Whitehead
Ruby on Rails is very powerful, but it cannot do everything. There are many features that are too experimental, out of scope of the Rails core, or even blatantly contrary to the way Rails was designed (it is opinionated software, after all). The core team cannot and would not include everything that anybody wants in Rails.
Luckily, Rails comes with a very flexible extension system. Rails plugins allow developers to extend or override nearly any part of the Rails framework, and share these modifications with others in an encapsulated and reusable manner.
By default, plugins are loaded from directories under vendor/plugins in the Rails application root. Should you need to change or add to these paths, the plugin_paths configuration item contains the plugin load paths:
	config.plugin_paths += [File.join(RAILS_ROOT, 'vendor', 'other_plugins')]
By default, plugins are loaded in alphabetical order; attachment_fu is loaded before http_authentication. If the plugins have dependencies on each other, a manual loading order can be specified with the plugins configuration element:
	config.plugins = %w(prerequisite_plugin actual_plugin)
Any plugins not specified in config.plugins will not be loaded. However, if the last plugin specified is the symbol :all, Rails will load all remaining plugins at that point. Rails accepts either symbols or strings as plugin names here.
	config.plugins = [ :prerequisite_plugin, :actual_plugin, :all ]
The plugin locator searches for plugins under the configured paths, recursively. Because a recursive search is performed, you can organize plugins into directories; for example, vendor/plugins/active_record_acts and vendor/plugins/view_extensions.
The actual plugin locating and loading system is extensible, and you can write your own strategies. The locator (which by default is
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
About Plugins
By default, plugins are loaded from directories under vendor/plugins in the Rails application root. Should you need to change or add to these paths, the plugin_paths configuration item contains the plugin load paths:
	config.plugin_paths += [File.join(RAILS_ROOT, 'vendor', 'other_plugins')]
By default, plugins are loaded in alphabetical order; attachment_fu is loaded before http_authentication. If the plugins have dependencies on each other, a manual loading order can be specified with the plugins configuration element:
	config.plugins = %w(prerequisite_plugin actual_plugin)
Any plugins not specified in config.plugins will not be loaded. However, if the last plugin specified is the symbol :all, Rails will load all remaining plugins at that point. Rails accepts either symbols or strings as plugin names here.
	config.plugins = [ :prerequisite_plugin, :actual_plugin, :all ]
The plugin locator searches for plugins under the configured paths, recursively. Because a recursive search is performed, you can organize plugins into directories; for example, vendor/plugins/active_record_acts and vendor/plugins/view_extensions.
The actual plugin locating and loading system is extensible, and you can write your own strategies. The locator (which by default is Rails::Plugin::FileSystemLocator) searches for plugins; the loader (by default Rails::Plugin::Loader) determines whether a directory contains a plugin and does the work of loading it.
To write your own locators and loaders, examine railties/lib/rails/plugin/locator.rb and railties/lib/rails/plugin/loader.rb. The locators (more than one locator can be used) and loader can be changed with configuration directives:
	config.plugin_locators += [MyPluginLocator]
	config.plugin_loader = MyPluginLoader
Plugins are most often installed with the built-in Rails plugin tool, script/plugin. This plugin tool has several commands:
discover/source/unsource/sources
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Writing Plugins
Once you know how to extend Rails by opening classes, it is easy to write a plugin. First, let's look at the directory structure of a typical plugin (see ).
Figure : Directory structure of a typical plugin
There are several files and directories involved in a Rails plugin:
about.yml (not shown)
This is the newest feature of Rails plugins—embedded metadata. Right now, this feature works only with RaPT. The command rapt about plugin_name will give a summary of the plugin's information. In the future, more features are expected; right now, it exists for informational purposes. Metadata is stored in the about.yml file; here is an example from acts_as_attachment:
	author: technoweenie 
	summary: File upload handling plugin.
	homepage: http://technoweenie.stikipad.com
	plugin: http://svn.techno-weenie.net/projects/plugins/acts_as_attachment
	license: MIT
	version: 0.3a
	rails_version: 1.1.2+
init.rb
This is a Ruby file run upon initialization of the plugin. Typically, it will require files from the lib/ directory. As many plugins patch core functionality, init.rb may extend core classes with extensions from the plugin:
	require 'my_plugin'
	ActionController::Base.send :include, MyPlugin::ControllerExtensions
The send hack is needed here because Module#include is a private method and, at least for now, send bypasses access control on the receiver.
install.rb (not shown)
This hook is run when the plugin is installed with one of the automated plugin installation tools such as script/plugin or RaPT. It is a good idea not to do any-thing mission-critical in this file, as it will not be run if the plugin is installed manually (by checking out the source to a directory under vendor/plugins).
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Plugin Examples
To illustrate the flexibility and design of a typical Rails plugin, we will examine some of the plugins available from the rubyonrails.org Subversion repository. Most of these plugins are used fairly commonly; many of them are used in 37signals applications. Consider them the "standard library" of Rails. They are all available from http://svn.rubyonrails.org/rails/plugins
Plugins can be very simple in structure. For example, consider David Heinemeier Hansson's account_location plugin. This plugin provides controller and helper methods to support using part of the domain name as an account name (for example, to support customers with domain names of customer1.example.com and customer2.example.com, using customer1 and customer2 as keys to look up the account information). To use the plugin, include AccountLocation in one or more of your controllers, which adds the appropriate instance methods:
	class ApplicationController < ActionController::Base
	  include AccountLocation
	end

	puts ApplicationController.instance_methods.grep /^account/
	=> ["account_domain", "account_subdomain", "account_host", "account_url"]
Including the AccountLocation module in the controller allows you to access various URL options from the controller and the view. For example, to set the @account variable from the subdomain on each request:
	class ApplicationController < ActionController::Base
	  include AccountLocation
	  before_filter :find_account

	  protected

	  def find_account
	    @account = Account.find_by_username(account_subdomain)
	  end
	end
The account_location plugin has no init.rb; nothing needs to be set up on load, as all functionality is encapsulated in the AccountLocation module. Here is the implementation, in lib/account_location.rb (minus some license text):
	module AccountLocation
	  def self.included(controller)
	    controller.helper_method(:account_domain, :account_subdomain,
	      :account_host, :account_url)
	  end

	  protected

	  def default_account_subdomain
	    @account.username if @account && @account.respond_to?(:username)
	  end

	  def account_url(account_subdomain = default_account_subdomain,
	      use_ssl = request.ssl?)
	    (use_ssl ? "https://" : "http://") + account_host(account_subdomain)
	  end

	  def account_host(account_subdomain = default_account_subdomain)
	    account_host = ""
	    account_host << account_subdomain + "."
	    account_host << account_domain
	  end

	  def account_domain
	    account_domain = ""
	    account_domain << request.subdomains[1..-1].join(".") +
	      "." if request.subdomains.size > 1
	    account_domain << request.domain + request.port_string
	  end

	  def account_subdomain
	    request.subdomains.first
	  end
	end
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Testing Plugins
Like the rest of Rails, plugins have very mature testing facilities. However, plugin tests usually require a bit more work than standard Rails tests, as the tests are designed to be run on their own, outside of the Rails framework. Some things to keep in mind when writing tests for plugins:
  • Unlike in the Rails plugin initializer, when running tests, load paths are not set up automatically, and Dependencies does not load missing constants for you. You need to manually set up the load paths and require any parts of the plugin that you will be testing, as in this example from the HTTP Authentication plugin:
    	$LOAD_PATH << File.dirname(__FILE__) + '/../lib/'
    	require 'http_authentication'
    
  • Similarly, the plugin's init.rb file is not loaded, so you must set up anything your tests need, such as including your plugin's modules in the TestCase class:
    	class HttpBasicAuthenticationTest < Test::Unit::TestCase
    	  include HttpAuthentication::Basic
    
    	  # …
    	end
    
  • You must usually recreate (mock or stub) any Rails functionality involved in your test. In the case of the HTTP Authentication plugin, it would be too much overhead to load the entire ActionController framework for the tests. The functionality being tested is very simple, and requires very little of ActionController:
    	def test_authentication_request
    	  authentication_request(@controller, "Megaglobalapp")
    	  assert_equal 'Basic realm="Megaglobalapp"',
    	               @controller.headers["WWW-Authenticate"] 
    	  assert_equal :unauthorized, @controller.renders.first[:status] 
    	end
    
    To support this limited subset of ActionController's features, the test's setup method creates a stub controller:
    	def setup
    	  @controller = Class.new do
    	    attr_accessor :headers, :renders
    
    	    def initialize
    	      @headers, @renders = {}, []
    	    end
    
    	    def request
    	      Class.new do
    	        def env
    	          {'HTTP_AUTHORIZATION' => 
    	            HttpAuthentication::Basic.encode_credentials("dhh", "secret") }
    	        end
    	      end.new
    	    end
    
    	    def render(options)
    	      self.renders << options
    	    end
    	  end.new
    	end
    
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Further Reading
Geoffrey Grosenbach has a two-part article on Rails plugins, including some information on writing plugins. The two parts are available from the following:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 4: Database
All non-trivial abstractions, to some degree, are leaky.
—Joel Spolsky
For many developers, Rails starts with the database. One of the most compelling features of Rails is ActiveRecord, the object-relational mapping(ORM) layer. ActiveRecord does such a good job of hiding the gory details of SQL from the programmer that it almost seems like magic.
However, as Joel Spolsky says, all abstractions are leaky. There is no perfectly transparent ORM system, and there never will be, due to the fundamentally different nature of the object-oriented and relational models. Ignore the underlying database at your own peril.
The Rails community has been built around the MySQL database management system (DBMS ) for years. However, there are still a lot of misconceptions surrounding DBMSs, especially when used with Rails. While MySQL has its place, it is certainly not the only option. In the past few years, support for other databases has vastly grown. I encourage you to keep an open mind throughout this chapter, and weigh all criteria before making a decision on a DBMS.
Rails supports many DBMSs; at the time of this writing, DB2, Firebird, FrontBase, MySQL, OpenBase, Oracle, PostgreSQL, SQLite, Microsoft SQL Server, and Sybase are supported. You will probably know if you need to use a DBMS other than the ones mentioned here. Check the RDoc for the connection adapter for any caveats specific to your DBMS; some features such as migrations are only supported on a handful of connection adapters.
I list PostgreSQL first because it is my platform of choice. It is one of the most advanced open source databases available today. It has a long history, dating back to the University of California at Berkeley's Ingres project from the early 1980s. In contrast to MySQL, Postgres has supported advanced features such as triggers, stored procedures, custom data types, and transactions for much longer.
PostgreSQL's support for concurrency is more mature than MySQL's. Postgres supports multiversion concurrency control (MVCC), which is even more advanced than row-level locking. MVCC can isolate transactions, using timestamps to give each concurrent transaction its own snapshot of the data set. Under the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Database Management Systems
The Rails community has been built around the MySQL database management system (DBMS ) for years. However, there are still a lot of misconceptions surrounding DBMSs, especially when used with Rails. While MySQL has its place, it is certainly not the only option. In the past few years, support for other databases has vastly grown. I encourage you to keep an open mind throughout this chapter, and weigh all criteria before making a decision on a DBMS.
Rails supports many DBMSs; at the time of this writing, DB2, Firebird, FrontBase, MySQL, OpenBase, Oracle, PostgreSQL, SQLite, Microsoft SQL Server, and Sybase are supported. You will probably know if you need to use a DBMS other than the ones mentioned here. Check the RDoc for the connection adapter for any caveats specific to your DBMS; some features such as migrations are only supported on a handful of connection adapters.
I list PostgreSQL first because it is my platform of choice. It is one of the most advanced open source databases available today. It has a long history, dating back to the University of California at Berkeley's Ingres project from the early 1980s. In contrast to MySQL, Postgres has supported advanced features such as triggers, stored procedures, custom data types, and transactions for much longer.
PostgreSQL's support for concurrency is more mature than MySQL's. Postgres supports multiversion concurrency control (MVCC), which is even more advanced than row-level locking. MVCC can isolate transactions, using timestamps to give each concurrent transaction its own snapshot of the data set. Under the Serializable isolation level, this prevents such problems as dirty reads, nonrepeatable reads, and phantom reads. See the upcoming sidebar, "Multiversion Concurrency Control," for more information about MVCC.
One advantage that PostgreSQL may have in the enterprise is its similarity to commercial enterprise databases such as Oracle, MS SQL Server, or DB2. Although Postgres is not by any means a clone or emulation of any commercial database, it will nevertheless be familiar to programmers and DBAs who have experience with one of the commercial databases. It will also likely be easier to migrate an application from Postgres to (say) Oracle than from MySQL to Oracle.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Large/Binary Objects
Sooner or later, many web applications must deal with the issue of LOB (large object) data. LOB data may be small, but it is usually large compared to other attributes being stored (tens of kilobytes to hundreds of gigabytes or larger). The defining characteristic of LOB data, however, is that the application has no knowledge of the semantics of the internal structure of the data.
The canonical example is image data; a web application usually has no need to know the data in a JPEG file representing a user's avatar as long as it can send it to the client, replace it, and delete it when needed.
LOB storage is usually divided into CLOB (character large object) for text data and BLOB (binary large object) for everything else. Some DBMSs separate the two as separate data types. CLOB types can often be indexed, collated, and searched; BLOBs cannot.
The DBA types among us might prefer database storage of large objects. From a theoretical standpoint, storing binary data in the database is the most clean and straight-forward solution. It offers some immediate advantages:
  • All of your application data is in the same place: the database. There is only one interface to the data, and one program is responsible for managing the data in all its forms.
  • You have greater flexibility with access control, which really helps when working with large-scale projects. DBMS permitting, different permissions may be assigned to different tables within the same database.
  • The binary data is not tied to a physical file path; when using filesystem storage, you must update the file paths in the referring database if you move the storage location.
There are many practical considerations, though, depending on your DBMS's implementation of large objects.

PostgreSQL

PostgreSQL has some downright weird support for binary data. There are two ways to store binary data in a PostgreSQL database: the BYTEA data type and large objects.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Advanced Database Features
Among Rails programmers, advanced database features are often a point of contention. Some contend that constraints, triggers, and procedures are essential; some shun them completely, saying that intelligence belongs in the application only. I am sympathetic to the argument that all business logic belongs in the application; it is nearly impossible to make agile changes to changing requirements when logic is split between two locations. Still, I believe that constraints, triggers, and even stored procedures have their place in enterprise applications. In order to explain why, we'll have to examine a distinction that comes up often in relation to this debate: the difference between application and integration databases.
Martin Fowler differentiates between application databases and integration data-bases. The basic distinction is that an integration database is shared among many applications, while an application database "belongs" to the one application using it.
In this sense, "application" can mean one program or multiple programs within an application boundary (the same logical application). Usually this distinction refers to how the schema is organized; in Rails, integration databases are often referred to as databases with "legacy schemas." In application databases, integration can still be performed through messaging at the application layer rather than the database layer.
Rails is opinionated about how your database schemas should be structured: the primary key should be id, foreign keys should be thing_id, and table names should be plural. This is not database bigotry; Rails has to choose a sensible default for the "convention over configuration" paradigm to be effective. It is relatively painless to change almost any of these defaults. Rails plays nice with integration databases.
Many Rails developers shun integration databases as unnecessary; they maintain that all integration should be done at the application layer. Some take that a step further and state that data integrity checking belongs in the application only, to keep all business logic in the same place. Although this might be ideal, the real world is not always that nice. Even if all integration can be done at the application level, there are still plenty of valid reasons to use database constraints.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Connecting to Multiple Databases
Content preview·Buy PDF of this chapter|