By Brad Ediger
Book Price: $34.99 USD
£21.99 GBP
PDF Price: $27.99
Cover | Table of Contents | Colophon
Simplicity is prerequisite for reliability.
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.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.klass
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.)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.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
method_missing:method_missing is at least two to three times as expensive in time as conventional dispatch.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.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.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
Person example, where we want to time several expensive methods:class Person def refresh # ... end def dup # ... end end
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
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
_without_timing alias).Person.trace method:p = Person.new p.refresh # => (...) Person.start_trace p.refresh # => (...) # -> refresh: 0.500 s. Person.end_trace p.refresh # => (...)
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.[Programs] must be written for people to read, and only incidentally for machines to execute.—H. Abelson and G. SussmannStructure and Interpretation of Computer Programs, MIT Press, 1985
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 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).sort_byDependencies 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.Store will be created as an empty module, by the following process:Store.const_missing.const_missing calls Dependencies.load_missing_constant(Object,:Store).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.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.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 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
render(:xml => …) and render(:json => …) are new synonyms for render(:text => …) that change the response's MIME type appropriately.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]]
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
ActionController::Base.perform_caching = true
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:check_ruby_version: Ensures that Ruby1.8.2 or above (but not 1.8.3) is being used.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.require_frameworks: Loads each framework listed in the frameworks configuration option. If the framework path was not specified in Civilization advances by extending the number of important operations which we can perform without thinking of them.
plugin_paths configuration item contains the plugin load paths:config.plugin_paths += [File.join(RAILS_ROOT, 'vendor', 'other_plugins')]
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)
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 ]
plugin_paths configuration item contains the plugin load paths:config.plugin_paths += [File.join(RAILS_ROOT, 'vendor', 'other_plugins')]
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)
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 ]
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.config.plugin_locators += [MyPluginLocator] config.plugin_loader = MyPluginLoader
script/plugin. This plugin tool has several commands:discover/source/unsource/sources
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+
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
send hack is needed here because Module#include is a private method and, at least for now, send bypasses access control on the receiver. 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).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"]
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
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
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'
TestCase class:class HttpBasicAuthenticationTest < Test::Unit::TestCase include HttpAuthentication::Basic # … end
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
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
All non-trivial abstractions, to some degree, are leaky.
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.