diff options
24 files changed, 1458 insertions, 506 deletions
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 1615f135b4..b81925c7e7 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -380,7 +380,7 @@ module ActionView # accepts_nested_attributes_for :address, :allow_destroy => true # end # - # Now, when you use a form element with the <tt>_delete</tt> parameter, + # Now, when you use a form element with the <tt>_destroy</tt> parameter, # with a value that evaluates to +true+, you will destroy the associated # model (eg. 1, '1', true, or 'true'): # @@ -388,7 +388,7 @@ module ActionView # ... # <% person_form.fields_for :address do |address_fields| %> # ... - # Delete: <%= address_fields.check_box :_delete %> + # Delete: <%= address_fields.check_box :_destroy %> # <% end %> # <% end %> # @@ -461,14 +461,14 @@ module ActionView # end # # This will allow you to specify which models to destroy in the - # attributes hash by adding a form element for the <tt>_delete</tt> + # attributes hash by adding a form element for the <tt>_destroy</tt> # parameter with a value that evaluates to +true+ # (eg. 1, '1', true, or 'true'): # # <% form_for @person, :url => { :action => "update" } do |person_form| %> # ... # <% person_form.fields_for :projects do |project_fields| %> - # Delete: <%= project_fields.check_box :_delete %> + # Delete: <%= project_fields.check_box :_destroy %> # <% end %> # <% end %> def fields_for(record_or_name_or_array, *args, &block) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 02ad637509..d3e614b98f 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -149,7 +149,7 @@ module ActionView # end # # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>): - # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {:prompt => true}) + # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, :prompt => true) # # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return: # <select name="post[author_id]"> diff --git a/activemodel/README b/activemodel/README index 7c9c754a8e..cf103b8d6d 100644 --- a/activemodel/README +++ b/activemodel/README @@ -12,32 +12,55 @@ Active Model provides a known set of interfaces that your objects can implement to then present a common interface to the Action Pack helpers. You can include functionality from the following modules: -* Adding callbacks to your class +* Adding attribute magic to your objects - class MyClass - extend ActiveModel::Callbacks - define_model_callbacks :create + Add prefixes and suffixes to defined attribute methods... + + class Person + include ActiveModel::AttributeMethods + + attribute_method_prefix 'clear_' + define_attribute_methods [:name, :age] + + attr_accessor :name, :age + + def clear_attribute(attr) + send("#{attr}=", nil) + end + end + + ...gives you clear_name, clear_age. + + {Learn more}[link:classes/ActiveModel/AttributeMethods.html] + +* Adding callbacks to your objects - def create - _run_create_callbacks do - # Your create action methods here - end - end - end - - ...gives you before_create, around_create and after_create class methods that - wrap your create method. + class Person + extend ActiveModel::Callbacks + define_model_callbacks :create + + def create + _run_create_callbacks do + # Your create action methods here + end + end + end + + ...gives you before_create, around_create and after_create class methods that + wrap your create method. {Learn more}[link:classes/ActiveModel/CallBacks.html] * For classes that already look like an Active Record object - class MyClass + class Person include ActiveModel::Conversion end ...returns the class itself when sent :to_model + {Learn more}[link:classes/ActiveModel/Conversion.html] + * Tracking changes in your object Provides all the value tracking features implemented by ActiveRecord... @@ -55,3 +78,141 @@ functionality from the following modules: {Learn more}[link:classes/ActiveModel/Dirty.html] +* Adding +errors+ support to your object + + Provides the error messages to allow your object to interact with Action Pack + helpers seamlessly... + + class Person + + def initialize + @errors = ActiveModel::Errors.new(self) + end + + attr_accessor :name + attr_reader :errors + + def validate! + errors.add(:name, "can not be nil") if name == nil + end + + def ErrorsPerson.human_attribute_name(attr, options = {}) + "Name" + end + + end + + ... gives you... + + person.errors.full_messages + # => ["Name Can not be nil"] + person.errors.full_messages + # => ["Name Can not be nil"] + + {Learn more}[link:classes/ActiveModel/Errors.html] + +* Testing the compliance of your object + + Use ActiveModel::Lint to test the compliance of your object to the + basic ActiveModel API... + + {Learn more}[link:classes/ActiveModel/Lint/Tests.html] + +* Providing a human face to your object + + ActiveModel::Naming provides your model with the model_name convention + and a human_name attribute... + + class NamedPerson + extend ActiveModel::Naming + end + + ...gives you... + + NamedPerson.model_name #=> "NamedPerson" + NamedPerson.model_name.human #=> "Named person" + + {Learn more}[link:classes/ActiveModel/Naming.html] + +* Adding observer support to your objects + + ActiveModel::Observers allows your object to implement the Observer + pattern in a Rails App and take advantage of all the standard observer + functions. + + {Learn more}[link:classes/ActiveModel/Observer.html] + +* Making your object serializable + + ActiveModel::Serialization provides a standard interface for your object + to provide to_json or to_xml serialization... + + s = SerialPerson.new + s.serializable_hash # => {"name"=>nil} + s.to_json # => "{\"name\":null}" + s.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person... + + {Learn more}[link:classes/ActiveModel/Serialization.html] + + +* Turning your object into a finite State Machine + + ActiveModel::StateMachine provides a clean way to include all the methods + you need to transform your object into a finite State Machine... + + light = TrafficLight.new + light.current_state #=> :red + light.change_color! #=> true + light.current_state #=> :green + + {Learn more}[link:classes/ActiveModel/StateMachine.html] + +* Integrating with Rail's internationalization (i18n) handling through + ActiveModel::Translations... + + class Person + extend ActiveModel::Translation + end + + {Learn more}[link:classes/ActiveModel/Translation.html] + +* Providing a full Validation stack for your objects... + + class Person + include ActiveModel::Validations + + attr_accessor :first_name, :last_name + + validates_each :first_name, :last_name do |record, attr, value| + record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z + end + end + + person = Person.new(:first_name => 'zoolander') + person.valid? #=> false + + {Learn more}[link:classes/ActiveModel/Validations.html] + +* Make custom validators + + class Person + include ActiveModel::Validations + validates_with HasNameValidator + attr_accessor :name + end + + class HasNameValidator < ActiveModel::Validator + def validate(record) + record.errors[:name] = "must exist" if record.name.blank? + end + end + + p = ValidatorPerson.new + p.valid? #=> false + p.errors.full_messages #=> ["Name must exist"] + p.name = "Bob" + p.valid? #=> true + + {Learn more}[link:classes/ActiveModel/Validator.html] + +
\ No newline at end of file diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 977a101277..32ddf1d579 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -5,10 +5,51 @@ module ActiveModel class MissingAttributeError < NoMethodError end + # <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and suffixes + # to your methods as well as handling the creation of Active Record like class methods + # such as +table_name+. + # + # The requirements to implement ActiveModel::AttributeMethods are: + # + # * <tt>include ActiveModel::AttributeMethods</tt> in your object + # * Call each Attribute Method module method you want to add, such as + # attribute_method_suffix or attribute_method_prefix + # * Call <tt>define_attribute_methods</tt> after the other methods are + # called. + # * Define the various generic +_attribute+ methods that you have declared + # + # A minimal implementation could be: + # + # class Person + # + # include ActiveModel::AttributeMethods + # + # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!' + # attribute_method_suffix '_contrived?' + # attribute_method_prefix 'clear_' + # define_attribute_methods ['name'] + # + # attr_accessor :name + # + # private + # + # def attribute_contrived?(attr) + # true + # end + # + # def clear_attribute(attr) + # send("#{attr}=", nil) + # end + # + # def reset_attribute_to_default!(attr) + # send("#{attr}=", "Default Name") + # end + # + # end + # module AttributeMethods extend ActiveSupport::Concern - # Declare and check for suffixed attribute methods. module ClassMethods # Defines an "attribute" method (like +inheritance_column+ or # +table_name+). A new (class) method will be created with the @@ -22,12 +63,27 @@ module ActiveModel # # Example: # - # class A < ActiveRecord::Base + # class Person + # + # include ActiveModel::AttributeMethods + # + # cattr_accessor :primary_key + # cattr_accessor :inheritance_column + # # define_attr_method :primary_key, "sysid" # define_attr_method( :inheritance_column ) do # original_inheritance_column + "_id" # end + # # end + # + # Provivdes you with: + # + # AttributePerson.primary_key + # # => "sysid" + # AttributePerson.inheritance_column = 'address' + # AttributePerson.inheritance_column + # # => 'address_id' def define_attr_method(name, value=nil, &block) sing = metaclass sing.send :alias_method, "original_#{name}", name @@ -54,19 +110,25 @@ module ActiveModel # # For example: # - # class Person < ActiveRecord::Base + # class Person + # + # include ActiveModel::AttributeMethods + # attr_accessor :name # attribute_method_prefix 'clear_' + # define_attribute_methods [:name] # # private - # def clear_attribute(attr) - # ... - # end + # + # def clear_attribute(attr) + # send("#{attr}=", nil) + # end # end # - # person = Person.find(1) - # person.name # => 'Gem' + # person = Person.new + # person.name = "Bob" + # person.name # => "Bob" # person.clear_name - # person.name # => '' + # person.name # => nil def attribute_method_prefix(*prefixes) attribute_method_matchers.concat(prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix }) undefine_attribute_methods @@ -86,18 +148,24 @@ module ActiveModel # # For example: # - # class Person < ActiveRecord::Base + # class Person + # + # include ActiveModel::AttributeMethods + # attr_accessor :name # attribute_method_suffix '_short?' + # define_attribute_methods [:name] # # private - # def attribute_short?(attr) - # ... - # end + # + # def attribute_short?(attr) + # send(attr).length < 5 + # end # end # - # person = Person.find(1) - # person.name # => 'Gem' - # person.name_short? # => true + # person = Person.new + # person.name = "Bob" + # person.name # => "Bob" + # person.name_short? # => true def attribute_method_suffix(*suffixes) attribute_method_matchers.concat(suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix }) undefine_attribute_methods @@ -118,16 +186,21 @@ module ActiveModel # # For example: # - # class Person < ActiveRecord::Base + # class Person + # + # include ActiveModel::AttributeMethods + # attr_accessor :name # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!' + # define_attribute_methods [:name] # # private - # def reset_attribute_to_default!(attr) - # ... - # end + # + # def reset_attribute_to_default!(attr) + # ... + # end # end # - # person = Person.find(1) + # person = Person.new # person.name # => 'Gem' # person.reset_name_to_default! # person.name # => 'Gemma' @@ -146,6 +219,30 @@ module ActiveModel end end + # Declares a the attributes that should be prefixed and suffixed by + # ActiveModel::AttributeMethods. + # + # To use, pass in an array of attribute names (as strings or symbols), + # be sure to declare +define_attribute_methods+ after you define any + # prefix, suffix or affix methods, or they will not hook in. + # + # class Person + # + # include ActiveModel::AttributeMethods + # attr_accessor :name, :age, :address + # attribute_method_prefix 'clear_' + # + # # Call to define_attribute_methods must appear after the + # # attribute_method_prefix, attribute_method_suffix or + # # attribute_method_affix declares. + # define_attribute_methods [:name, :age, :address] + # + # private + # + # def clear_attribute(attr) + # ... + # end + # end def define_attribute_methods(attr_names) return if attribute_methods_generated? attr_names.each do |attr_name| @@ -168,6 +265,7 @@ module ActiveModel @attribute_methods_generated = true end + # Removes all the preiously dynamically defined methods from the class def undefine_attribute_methods generated_attribute_methods.module_eval do instance_methods.each { |m| undef_method(m) } @@ -183,6 +281,7 @@ module ActiveModel end end + # Returns true if the attribute methods defined have been generated. def attribute_methods_generated? @attribute_methods_generated ||= nil end diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 2e5bcab070..76e6ad93a7 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -2,9 +2,68 @@ require 'active_support/core_ext/string/inflections' require 'active_support/ordered_hash' module ActiveModel + # Provides a modified +OrderedHash+ that you can include in your object + # for handling error messages and interacting with Action Pack helpers. + # + # A minimal implementation could be: + # + # class Person + # + # # Required dependency for ActiveModel::Errors + # extend ActiveModel::Naming + # + # def initialize + # @errors = ActiveModel::Errors.new(self) + # end + # + # attr_accessor :name + # attr_reader :errors + # + # def validate! + # errors.add(:name, "can not be nil") if name == nil + # end + # + # # The following methods are needed to be minimally implemented + # + # def read_attribute_for_validation(attr) + # send(attr) + # end + # + # def ErrorsPerson.human_attribute_name(attr, options = {}) + # attr + # end + # + # def ErrorsPerson.lookup_ancestors + # [self] + # end + # + # end + # + # The last three methods are required in your object for Errors to be + # able to generate error messages correctly and also handle multiple + # languages. Of course, if you extend your object with ActiveModel::Translations + # you will not need to implement the last two. Likewise, using + # ActiveModel::Validations will handle the validation related methods + # for you. + # + # The above allows you to do: + # + # p = Person.new + # p.validate! # => ["can not be nil"] + # p.errors.full_messages # => ["name can not be nil"] + # # etc.. class Errors < ActiveSupport::OrderedHash include DeprecatedErrorMethods + # Pass in the instance of the object that is using the errors object. + # + # class Person + # def initialize + # @errors = ActiveModel::Errors.new(self) + # end + # end + # + # def initialize(base) @base = base super() @@ -13,6 +72,10 @@ module ActiveModel alias_method :get, :[] alias_method :set, :[]= + # When passed a symbol or a name of a method, returns an array of errors for the method. + # + # p.errors[:name] #=> ["can not be nil"] + # p.errors['name'] #=> ["can not be nil"] def [](attribute) if errors = get(attribute.to_sym) errors @@ -21,28 +84,73 @@ module ActiveModel end end + # Adds to the supplied attribute the supplied error message. + # + # p.errors[:name] = "must be set" + # p.errors[:name] #=> ['must be set'] def []=(attribute, error) self[attribute.to_sym] << error end + # Iterates through each error key, value pair in the error messages hash. + # Yields the attribute and the error for that attribute. If the attribute + # has more than one error message, yields once for each error message. + # + # p.errors.add(:name, "can't be blank") + # p.errors.each do |attribute, errors_array| + # # Will yield :name and "can't be blank" + # end + # + # p.errors.add(:name, "must be specified") + # p.errors.each do |attribute, errors_array| + # # Will yield :name and "can't be blank" + # # then yield :name and "must be specified" + # end def each each_key do |attribute| self[attribute].each { |error| yield attribute, error } end end + # Returns the number of error messages. + # + # p.errors.add(:name, "can't be blank") + # p.errors.size #=> 1 + # p.errors.add(:name, "must be specified") + # p.errors.size #=> 2 def size values.flatten.size end + # Returns an array of error messages, with the attribute name included + # + # p.errors.add(:name, "can't be blank") + # p.errors.add(:name, "must be specified") + # p.errors.to_a #=> ["name can't be blank", "name must be specified"] def to_a full_messages end + # Returns the number of error messages. + # p.errors.add(:name, "can't be blank") + # p.errors.count #=> 1 + # p.errors.add(:name, "must be specified") + # p.errors.count #=> 2 def count to_a.size end + # Returns an xml formatted representation of the Errors hash. + # + # p.errors.add(:name, "can't be blank") + # p.errors.add(:name, "must be specified") + # p.errors.to_xml #=> Produces: + # + # # <?xml version=\"1.0\" encoding=\"UTF-8\"?> + # # <errors> + # # <error>name can't be blank</error> + # # <error>name must be specified</error> + # # </errors> def to_xml(options={}) require 'builder' unless defined? ::Builder options[:root] ||= "errors" @@ -59,6 +167,7 @@ module ActiveModel # for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one # error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>. # If no +messsage+ is supplied, :invalid is assumed. + # # If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error). # If +message+ is a Proc, it will be called, allowing for things like Time.now to be used within an error def add(attribute, message = nil, options = {}) diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 1330bf7042..e8a39130a6 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -13,8 +13,7 @@ module ActiveModel module Lint module Tests - # valid? - # ------ + # == Responds to <tt>valid?</tt> # # Returns a boolean that specifies whether the object is in a valid or invalid # state. @@ -23,8 +22,7 @@ module ActiveModel assert_boolean model.valid?, "valid?" end - # new_record? - # ----------- + # == Responds to <tt>new_record?</tt> # # Returns a boolean that specifies whether the object has been persisted yet. # This is used when calculating the URL for an object. If the object is @@ -41,8 +39,7 @@ module ActiveModel assert_boolean model.destroyed?, "destroyed?" end - # naming - # ------ + # == Naming # # Model.model_name must returns a string with some convenience methods as # :human and :partial_path. Check ActiveModel::Naming for more information. @@ -57,9 +54,8 @@ module ActiveModel assert_kind_of String, model_name.plural end - # errors - # ------ - # + # == Errors Testing + # # Returns an object that has :[] and :full_messages defined on it. See below # for more details. diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 4cd68a0c89..b9fb5fe0c8 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -1,6 +1,7 @@ require 'active_support/inflector' module ActiveModel + class Name < String attr_reader :singular, :plural, :element, :collection, :partial_path alias_method :cache_key, :collection @@ -35,6 +36,21 @@ module ActiveModel end end + # ActiveModel::Naming is a module that creates a +model_name+ method on your + # object. + # + # To implement, just extend ActiveModel::Naming in your object: + # + # class BookCover + # exten ActiveModel::Naming + # end + # + # BookCover.model_name #=> "BookCover" + # BookCover.model_name.human #=> "Book cover" + # + # Providing the functionality that ActiveModel::Naming provides in your object + # is required to pass the ActiveModel Lint test. So either extending the provided + # method below, or rolling your own is required.. module Naming # Returns an ActiveModel::Name object for module. It can be # used to retrieve all kinds of naming-related information. @@ -42,4 +58,5 @@ module ActiveModel @_model_name ||= ActiveModel::Name.new(self) end end + end diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index d9d1ab8967..ed6fb47c7e 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -24,8 +24,9 @@ module ActiveModel # # Same as above, just using explicit class references # ActiveRecord::Base.observers = Cacher, GarbageCollector # - # Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is - # called during startup, and before each development request. + # Note: Setting this does not instantiate the observers yet. + # +instantiate_observers+ is called during startup, and before + # each development request. def observers=(*values) @observers = values.flatten end @@ -102,10 +103,12 @@ module ActiveModel # # == Observing a class that can't be inferred # - # Observers will by default be mapped to the class with which they share a name. So CommentObserver will - # be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer - # differently than the class you're interested in observing, you can use the Observer.observe class method which takes - # either the concrete class (Product) or a symbol for that class (:product): + # Observers will by default be mapped to the class with which they share a + # name. So CommentObserver will be tied to observing Comment, ProductManagerObserver + # to ProductManager, and so on. If you want to name your observer differently than + # the class you're interested in observing, you can use the Observer.observe class + # method which takes either the concrete class (Product) or a symbol for that + # class (:product): # # class AuditObserver < ActiveModel::Observer # observe :account @@ -115,7 +118,8 @@ module ActiveModel # end # end # - # If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments: + # If the audit observer needs to watch more than one kind of object, this can be + # specified with multiple arguments: # # class AuditObserver < ActiveModel::Observer # observe :account, :balance @@ -125,7 +129,8 @@ module ActiveModel # end # end # - # The AuditObserver will now act on both updates to Account and Balance by treating them both as records. + # The AuditObserver will now act on both updates to Account and Balance by treating + # them both as records. # class Observer include Singleton @@ -144,7 +149,7 @@ module ActiveModel # # class AuditObserver < ActiveModel::Observer # def self.observed_classes - # [AccountObserver, BalanceObserver] + # [Account, Balance] # end # end def observed_classes diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 4c0073f687..28f95f0cdc 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -2,6 +2,64 @@ require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/slice' module ActiveModel + + # Provides a basic serialization to a serializable_hash for your object. + # + # A minimal implementation could be: + # + # class Person + # + # include ActiveModel::Serialization + # + # attr_accessor :name + # + # def attributes + # @attributes ||= {'name' => 'nil'} + # end + # + # end + # + # Which would provide you with: + # + # person = Person.new + # person.serializable_hash # => {"name"=>nil} + # person.name = "Bob" + # person.serializable_hash # => {"name"=>"Bob"} + # + # You need to declare some sort of attributes hash which contains the attributes + # you want to serialize and their current value. + # + # Most of the time though, you will want to include the JSON or XML + # serializations. Both of these modules automatically include the + # ActiveModel::Serialization module, so there is no need to explicitly + # include it. + # + # So a minimal implementation including XML and JSON would be: + # + # class Person + # + # include ActiveModel::Serializers::JSON + # include ActiveModel::Serializers::Xml + # + # attr_accessor :name + # + # def attributes + # @attributes ||= {'name' => 'nil'} + # end + # + # end + # + # Which would provide you with: + # + # person = Person.new + # person.serializable_hash # => {"name"=>nil} + # person.to_json # => "{\"name\":null}" + # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person... + # + # person.name = "Bob" + # person.serializable_hash # => {"name"=>"Bob"} + # person.to_json # => "{\"name\":\"Bob\"}" + # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person... module Serialization def serializable_hash(options = nil) options ||= {} diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 86149f1e5f..a185204680 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -85,8 +85,8 @@ module ActiveModel @options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s } end - # To replicate the behavior in ActiveRecord#attributes, - # <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set + # To replicate the behavior in ActiveRecord#attributes, <tt>:except</tt> + # takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set # for a N level model but is set for the N+1 level models, # then because <tt>:except</tt> is set to a default value, the second # level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if diff --git a/activemodel/lib/active_model/state_machine.rb b/activemodel/lib/active_model/state_machine.rb index 527794b34d..64b91c1659 100644 --- a/activemodel/lib/active_model/state_machine.rb +++ b/activemodel/lib/active_model/state_machine.rb @@ -1,4 +1,153 @@ module ActiveModel + + # ActiveModel::StateMachine provides methods that turn your object into a + # finite state machine, able to move from one state to another. + # + # A minimal implementation could be: + # + # class EmailMessage + # include ActiveModel::StateMachine + # + # state_machine do + # state :unread + # state :read + # end + # + # event :open_email do + # transitions :to => :read, :from => :unread + # end + # end + # + # === Examples + # + # class TrafficLight + # include ActiveModel::StateMachine + # + # attr_reader :runners_caught + # + # def initialize + # @runners_caught = 0 + # end + # + # state_machine do + # state :red + # state :green + # state :yellow + # state :blink + # + # event :change_color do + # transitions :to => :red, :from => [:yellow], + # :on_transition => :catch_runners + # transitions :to => :green, :from => [:red] + # transitions :to => :yellow, :from => [:green] + # end + # + # event :defect do + # transitions :to => :blink, :from => [:yellow, :red, :green] + # end + # + # event :repair do + # transitions :to => :red, :from => [:blink] + # end + # end + # + # def catch_runners + # @runners_caught += 1 + # end + # end + # + # light = TrafficLight.new + # light.current_state # => :red + # light.change_color! # => true + # light.current_state # => :green + # light.green? # => true + # light.change_color! # => true + # light.current_state # => :yellow + # light.red? # => false + # light.change_color! # => true + # light.runners_caught # => 1 + # + # * The initial state for TrafficLight is red which is the first state defined. + # + # TrafficLight.state_machine.initial_state # => :red + # + # * Call an event to transition a state machine, e.g. <tt>change_color!</tt>. + # You can call the event with or without the exclamation mark, however, the common Ruby + # idiom is to name methods that directly change the state of the receivier with + # an exclamation mark, so <tt>change_color!</tt> is preferred over <tt>change_color</tt>. + # + # light.current_state #=> :green + # light.change_color! #=> true + # light.current_state #=> :yellow + # + # * On a succesful transition to red (from yellow), the local +catch_runners+ + # method is executed + # + # light.current_state #=> :red + # light.change_color! #=> true + # light.runners_caught #=> 1 + # + # * The object acts differently depending on its current state, for instance, + # the change_color! method has a different action depending on the current + # color of the light + # + # light.change_color! #=> true + # light.current_state #=> :red + # light.change_color! #=> true + # light.current_state #=> :green + # + # * Get the possible events for a state + # + # TrafficLight.state_machine.events_for(:red) # => [:change_color, :defect] + # TrafficLight.state_machine.events_for(:blink) # => [:repair] + # + # The StateMachine also supports the following features : + # + # * Success callbacks on event transition + # + # event :sample, :success => :we_win do + # ... + # end + # + # * Enter and exit callbacks par state + # + # state :open, :enter => [:alert_twitter, :send_emails], :exit => :alert_twitter + # + # * Guards on transition + # + # event :close do + # # You may only close the store if the safe is locked!! + # transitions :to => :closed, :from => :open, :guard => :safe_locked? + # end + # + # * Setting the initial state + # + # state_machine :initial => :yellow do + # ... + # end + # + # * Named the state machine, to have more than one + # + # class Stated + # include ActiveModel::StateMachine + # + # strate_machine :name => :ontest do + # end + # + # state_machine do + # end + # end + # + # # Get the state of the <tt>:ontest</tt> state machine + # stat.current_state(:ontest) + # # Get the initial state + # Stated.state_machine(:ontest).initial_state + # + # * Changing the state + # + # stat.current_state(:default, :astate) # => :astate + # # But you must give the name of the state machine, here <tt>:default</tt> + # module StateMachine autoload :Event, 'active_model/state_machine/event' autoload :Machine, 'active_model/state_machine/machine' diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb index 2d2df269d0..2ab342ffac 100644 --- a/activemodel/lib/active_model/translation.rb +++ b/activemodel/lib/active_model/translation.rb @@ -1,6 +1,23 @@ require 'active_support/core_ext/hash/reverse_merge' module ActiveModel + + # ActiveModel::Translation provides integration between your object and + # the Rails internationalization (i18n) framework. + # + # A minimal implementation could be: + # + # class TranslatedPerson + # extend ActiveModel::Translation + # end + # + # TranslatedPerson.human_attribute_name('my_attribue') + # #=> "My attribute" + # + # This also provides the required class methods for hooking into the + # Rails internationalization API, including being able to define a + # class based i18n_scope and lookup_ancestors to find translations in + # parent classes. module Translation include ActiveModel::Naming @@ -18,8 +35,6 @@ module ActiveModel # Transforms attributes names into a more human format, such as "First name" instead of "first_name". # - # Example: - # # Person.human_attribute_name("first_name") # => "First name" # # Specify +options+ with additional translating options. diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 276472ea46..03733a9c89 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -3,6 +3,41 @@ require 'active_support/core_ext/hash/keys' require 'active_model/errors' module ActiveModel + + # Provides a full validation framework to your objects. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :first_name, :last_name + # + # validates_each :first_name, :last_name do |record, attr, value| + # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z + # end + # end + # + # Which provides you with the full standard validation stack that you + # know from ActiveRecord. + # + # person = Person.new + # person.valid? + # #=> true + # person.invalid? + # #=> false + # person.first_name = 'zoolander' + # person.valid? + # #=> false + # person.invalid? + # #=> true + # person.errors + # #=> #<OrderedHash {:first_name=>["starts with z."]}> + # + # Note that ActiveModel::Validations automatically adds an +errors+ method + # to your instances initialized with a new ActiveModel::Errors object, so + # there is no need for you to add this manually. + # module Validations extend ActiveSupport::Concern include ActiveSupport::Callbacks @@ -18,8 +53,10 @@ module ActiveModel # class Person # include ActiveModel::Validations # + # attr_accessor :first_name, :last_name + # # validates_each :first_name, :last_name do |record, attr, value| - # record.errors.add attr, 'starts with z.' if value[0] == ?z + # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z # end # end # diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 382a4cc98d..ad9729de00 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -1,5 +1,6 @@ module ActiveModel #:nodoc: - # A simple base class that can be used along with ActiveModel::Validations::ClassMethods.validates_with + # A simple base class that can be used along with + # +ActiveModel::Validations::ClassMethods.validates_with+ # # class Person # include ActiveModel::Validations @@ -28,7 +29,7 @@ module ActiveModel #:nodoc: # end # # class MyValidator < ActiveModel::Validator - # def validate + # def validate(record) # record # => The person instance being validated # options # => Any non-standard options passed to validates_with # end diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index df8aefea5a..c751e29908 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -1,8 +1,8 @@ class Module # Provides a delegate class method to easily expose contained objects' methods # as your own. Pass one or more methods (specified as symbols or strings) - # and the name of the target object as the final <tt>:to</tt> option (also a symbol - # or string). At least one method and the <tt>:to</tt> option are required. + # and the name of the target object via the <tt>:to</tt> option (also a symbol + # or string). At least one method and the <tt>:to</tt> option are required. # # Delegation is particularly useful with Active Record associations: # @@ -72,9 +72,9 @@ class Module # invoice.customer_name # => "John Doe" # invoice.customer_address # => "Vimmersvej 13" # - # If the object to which you delegate can be nil, you may want to use the - # :allow_nil option. In that case, it returns nil instead of raising a - # NoMethodError exception: + # If the delegate object is +nil+ an exception is raised, and that happens + # no matter whether +nil+ responds to the delegated method. You can get a + # +nil+ instead with the +:allow_nil+ option. # # class Foo # attr_accessor :bar @@ -124,7 +124,7 @@ class Module #{to}.__send__(#{method.inspect}, *args, &block) # client.__send__(:name, *args, &block) rescue NoMethodError # rescue NoMethodError if #{to}.nil? # if client.nil? - #{on_nil} + #{on_nil} # return # depends on :allow_nil else # else raise # raise end # end diff --git a/railties/guides/source/3_0_release_notes.textile b/railties/guides/source/3_0_release_notes.textile new file mode 100644 index 0000000000..43a27f6402 --- /dev/null +++ b/railties/guides/source/3_0_release_notes.textile @@ -0,0 +1,51 @@ +h2. Ruby on Rails 3.0 Release Notes + +Rails 3.0 is a landmark release as it delivers on the Merb/Rails merge promise made in December, 2008. Rails 3.0 provides many major upgrades to all of the major components of Rails, and now adds Active Model ORM abstraction as well as a consistent Plugin API giving developers full access to all the Rails internals that make Action Mailer, Action Controller, Action View, Active Record and Active Resource work. These release notes cover the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the "list of commits":http://github.com/rails/rails/commits/master in the main Rails repository on GitHub or review the +CHANGELOG+ files for the individual Rails components. + +endprologue. + +h3. Application Creation + + +h3. Application Architecture + + +h3. Documentation + + +h3. Ruby 1.9.1 Support + + +h3. Railties + + +h3. Action Controller + + +h3. Action Dispatch + + +h3. Action Pack + + +h3. Action View + + +h3. Active Model + + +h3. Active Record + + +h3. Active Resource + + +h3. Action Mailer + + +h3. Deprecated + + +h3. Credits + +Release notes compiled by "Mikel Lindsaar":http://lindsaar.net. This version of the Rails 3.0 release notes was compiled based on Rails version... diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index 46a28da8c4..bedca59c12 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -625,9 +625,9 @@ class ClientsController < ApplicationController # returns it. The user will get the PDF as a file download. def download_pdf client = Client.find(params[:id]) - send_data(generate_pdf, + send_data generate_pdf(client), :filename => "#{client.name}.pdf", - :type => "application/pdf") + :type => "application/pdf" end private diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 3073c3a7a5..562bc76135 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -587,82 +587,6 @@ If for whatever reason an application loads the definition of a mailer class and NOTE: Defined in +active_support/core_ext/class/delegating_attributes.rb+. -h4. Subclasses - -The +subclasses+ method returns the names of all subclasses of a given class as an array of strings. That comprises not only direct subclasses, but all descendants down the hierarchy: - -<ruby> -class C; end -C.subclasses # => [] - -Integer.subclasses # => ["Bignum", "Fixnum"] - -module M - class A; end - class B1 < A; end - class B2 < A; end -end - -module N - class C < M::B1; end -end - -M::A.subclasses # => ["N::C", "M::B2", "M::B1"] -</ruby> - -The order in which these class names are returned is unspecified. - -See also +Object#subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME. - -NOTE: Defined in +active_support/core_ext/class/removal.rb+. - -h4. Class Removal - -Roughly speaking, the +remove_class+ method removes the class objects passed as arguments: - -<ruby> -Class.remove_class(Hash, Dir) # => [Hash, Dir] -Hash # => NameError: uninitialized constant Hash -Dir # => NameError: uninitialized constant Dir -</ruby> - -More specifically, +remove_class+ attempts to remove constants with the same name as the passed class objects from their parent modules. So technically this method does not guarantee the class objects themselves are not still valid and alive somewhere after the method call: - -<ruby> -module M - class A; end - class B < A; end -end - -A2 = M::A - -M::A.object_id # => 13053950 -Class.remove_class(M::A) - -M::B.superclass.object_id # => 13053950 (same object as before) -A2.name # => "M::A" (name is hard-coded in object) -</ruby> - -WARNING: Removing fundamental classes like +String+ can result in really funky behaviour. - -The method +remove_subclasses+ provides a shortcut for removing all descendants of a given class, where "removing" has the meaning explained above: - -<ruby> -class A; end -class B1 < A; end -class B2 < A; end -class C < A; end - -A.subclasses # => ["C", "B2", "B1"] -A.remove_subclasses -A.subclasses # => [] -C # => NameError: uninitialized constant C -</ruby> - -See also +Object#remove_subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME. - -NOTE: Defined in +active_support/core_ext/class/removal.rb+. - h3. Extensions to +String+ h4. +squish+ @@ -816,7 +740,15 @@ NOTE: Defined in +active_support/core_ext/integer/inflections.rb+. h3. Extensions to +Float+ -... +h4. +round+ + +The builtin method +Float#round+ rounds a float to the nearest integer. Active Support adds an optional parameter to let you specify a precision: + +<ruby> +Math::E.round(4) # => 2.7183 +</ruby> + +NOTE: Defined in +active_support/core_ext/float/rounding.rb+. h3. Extensions to +BigDecimal+ @@ -1799,20 +1731,11 @@ NOTE: Defined in +active_support/core_ext/name_error.rb+. h3. Extensions to +LoadError+ -Rails hijacks +LoadError.new+ to return a +MissingSourceFile+ exception: +Active Support adds +is_missing?+ to +LoadError+, and also assigns that class to the constant +MissingSourceFile+ for backwards compatibility. -<shell> -$ ruby -e 'require "nonexistent"' -...: no such file to load -- nonexistent (LoadError) -... -$ script/runner 'require "nonexistent"' -...: no such file to load -- nonexistent (MissingSourceFile) -... -</shell> +Given a path name +is_missing?+ tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension). -The class +MissingSourceFile+ is a subclass of +LoadError+, so any code that rescues +LoadError+ as usual still works as expected. Point is these exception objects respond to +is_missing?+, which given a path name tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension). - -For example, when an action of +PostsController+ is called Rails tries to load +posts_helper.rb+, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist, but it in turn requires another library that is missing. In that case Rails must reraise the exception. The method +is_missing?+ provides a way to distinguish both cases: +For example, when an action of +PostsController+ is called Rails tries to load +posts_helper.rb+, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist and in turn requires another library that is missing. In that case Rails must reraise the exception. The method +is_missing?+ provides a way to distinguish both cases: <ruby> def default_helper_module! @@ -1820,7 +1743,7 @@ def default_helper_module! module_path = module_name.underscore helper module_path rescue MissingSourceFile => e - raise e unless e.is_missing? "#{module_path}_helper" + raise e unless e.is_missing? "helpers/#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" end diff --git a/railties/guides/source/contributing_to_rails.textile b/railties/guides/source/contributing_to_rails.textile index 805fcf1a32..1b6823a39a 100644 --- a/railties/guides/source/contributing_to_rails.textile +++ b/railties/guides/source/contributing_to_rails.textile @@ -76,21 +76,28 @@ TIP: You may want to "put your git branch name in your shell prompt":http://gith h4. Set up and Run the Tests All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. Rails needs the +mocha+ gem for running some tests, so install it with: + <shell> gem install mocha </shell> -For the tests that touch the database, this means creating the databases. If you're using MySQL: +For the tests that touch the database, this means creating test databases. If you're using MySQL, create a user named +rails+ with privileges on the test databases. + +<shell> +mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.* + to 'rails'@'localhost'; +mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.* + to 'rails'@'localhost'; +</shell> + +Enter this from the +activerecord+ directory to create the test databases: <shell> -mysql> create database activerecord_unittest; -mysql> create database activerecord_unittest2; -mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.* - to 'rails'@'localhost'; -mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.* - to 'rails'@'localhost'; +rake mysql:build_databases </shell> +NOTE: Using the rake task to create the test databases ensures they have the correct character set and collation. + If you’re using another database, check the files under +activerecord/test/connections+ in the Rails source code for default connection information. You can edit these files if you _must_ on your machine to provide different credentials, but obviously you should not push any such changes back to Rails. Now if you go back to the root of the Rails source on your machine and run +rake+ with no parameters, you should see every test in all of the Rails components pass. If you want to run the all ActiveRecord tests (or just a single one) with another database adapter, enter this from the +activerecord+ directory: diff --git a/railties/guides/source/credits.textile.erb b/railties/guides/source/credits.textile.erb index 49e390908f..28c70f2b39 100644 --- a/railties/guides/source/credits.textile.erb +++ b/railties/guides/source/credits.textile.erb @@ -43,6 +43,10 @@ p. We'd like to thank the following people for their tireless contributions to t Cássio Marques is a Brazilian software developer working with different programming languages such as Ruby, JavaScript, CPP and Java, as an independent consultant. He blogs at "/* CODIFICANDO */":http://cassiomarques.wordpress.com, which is mainly written in Portuguese, but will soon get a new section for posts with English translation. <% end %> +<% author('James Miller', 'bensie') do %> + James Miller is a software developer for "JK Tech":http://www.jk-tech.com in San Diego, CA. Find me on GitHub, Gmail, Twitter, and Freenode as bensie. +<% end %> + <% author('Emilio Tagua', 'miloops') do %> Emilio Tagua -- a.k.a. miloops -- is an Argentinian entrepreneur, developer, open source contributor and Rails evangelist. Cofounder of "Eventioz":http://eventioz.com. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as miloops. <% end %> @@ -50,3 +54,7 @@ p. We'd like to thank the following people for their tireless contributions to t <% author('Heiko Webers', 'hawe') do %> Heiko Webers is the founder of "bauland42":http://www.bauland42.de, a German web application security consulting and development company focused on Ruby on Rails. He blogs at the "Ruby on Rails Security Project":http://www.rorsecurity.info. After 10 years of desktop application development, Heiko has rarely looked back. <% end %> + +<% author('Mikel Lindsaar', 'raasdnil') do %> + Mikel Lindsaar has been working with Rails since 2006 and is the author of the Ruby Mail gem and core contributor (he helped re-write ActionMailer's API). Mikel has a "blog":http://lindsaar.net/ and "tweets":http://twitter.com/raasdnil. +<% end %> diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index bd6dbda199..c173748944 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -9,13 +9,13 @@ This guide covers getting up and running with Ruby on Rails. After reading it, y endprologue. -WARNING. This Guide is based on Rails 2.3.3. Some of the code shown here will not work in other versions of Rails. +WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in other versions of Rails. h3. This Guide Assumes This guide is designed for beginners who want to get started with a Rails application from scratch. It does not assume that you have any prior experience with Rails. However, to get the most out of it, you need to have some prerequisites installed: -* The "Ruby":http://www.ruby-lang.org/en/downloads language +* The "Ruby":http://www.ruby-lang.org/en/downloads language version 1.8.7 or higher * The "RubyGems":http://rubyforge.org/frs/?group_id=126 packaging system * A working installation of "SQLite":http://www.sqlite.org (preferred), "MySQL":http://www.mysql.com, or "PostgreSQL":http://www.postgresql.org @@ -91,7 +91,7 @@ Active Resource provides a framework for managing the connection between busines h5. Railties -Railties is the core Rails code that builds new Rails applications and glues the various frameworks together in any Rails application. +Railties is the core Rails code that builds new Rails applications and glues the various frameworks and plugins together in any Rails application. h5. Active Support @@ -134,8 +134,6 @@ NOTE. There are some special circumstances in which you might want to use an alt * If you're working on Windows, you may find it easier to install Instant Rails. Be aware, though, that "Instant Rails":http://instantrails.rubyforge.org/wiki/wiki.pl releases tend to lag seriously behind the actual Rails version. Also, you will find that Rails development on Windows is overall less pleasant than on other operating systems. If at all possible, we suggest that you install a Linux virtual machine and use that for Rails development, instead of using Windows. * If you want to keep up with cutting-edge changes to Rails, you'll want to clone the "Rails source code":http://github.com/rails/rails/tree/master from github. This is not recommended as an option for beginners, though. -WARNING. As of mid-2009, cloning the master branch will get you preliminary Rails 3.0 code. To follow along with this guide, you should clone the 2-3-stable branch instead. - h4. Creating the Blog Application Open a terminal, navigate to a folder where you have rights to create files, and type: @@ -167,10 +165,13 @@ $ cd blog In any case, Rails will create a folder in your working directory called <tt>blog</tt>. Open up that folder and explore its contents. Most of the work in this tutorial will happen in the <tt>app/</tt> folder, but here's a basic rundown on the function of each folder that Rails creates in a new application by default: |_.File/Folder|_.Purpose| -|README|This is a brief instruction manual for your application. Use it to tell others what your application does, how to set it up, and so on.| +|Gemfile|This file allows you to specify what gem dependencies are needed for your Rails application.| +|README.rdoc|This is a brief instruction manual for your application. Use it to tell others what your application does, how to set it up, and so on.| |Rakefile|This file contains batch jobs that can be run from the terminal.| |app/|Contains the controllers, models, and views for your application. You'll focus on this folder for the remainder of this guide.| +|bin/|Holds various executables needed for your Rails application.| |config/|Configure your application's runtime rules, routes, database, and more.| +|config.ru|Rack configuration for Rack based servers used to start the application.| |db/|Shows your current database schema, as well as the database migrations. You'll learn about migrations shortly.| |doc/|In-depth documentation for your application.| |lib/|Extended modules for your application (not covered in this guide).| @@ -179,7 +180,18 @@ In any case, Rails will create a folder in your working directory called <tt>blo |script/|Scripts provided by Rails to do recurring tasks, such as benchmarking, plugin installation, and starting the console or the web server.| |test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html| |tmp/|Temporary files| -|vendor/|A place for third-party code. In a typical Rails application, this includes Ruby Gems, the Rails source code (if you install it into your project) and plugins containing additional prepackaged functionality.| +|vendor/|A place for all third-party code. In a typical Rails application, this includes Ruby Gems, the Rails source code (if you install it into your project) and plugins containing additional prepackaged functionality.| + +h4. Installing the Required Gems + +Rails uses the _Bundler_ gem to populate the +vendor+ directory with all the gems your application depends on. As we don't need any special gems beyond the default, we just need to do the following: + +<shell> +$ gem install bundle +$ gem bundle +</shell> + +This will copy down the latest versions of all the gems you need to start a rails application. h4. Configuring a Database @@ -255,19 +267,11 @@ NOTE. Rake is a general-purpose command-runner that Rails uses for many things. h3. Hello, Rails! -One of the traditional places to start with a new language is by getting some text up on screen quickly. To do that in Rails, you need to create at minimum a controller and a view. Fortunately, you can do that in a single command. Enter this command in your terminal: - -<shell> -$ script/generate controller home index -</shell> - -TIP: If you're on Windows, or your Ruby is set up in some non-standard fashion, you may need to explicitly pass Rails +script+ commands to Ruby: +ruby script/generate controller home index+. +One of the traditional places to start with a new language is by getting some text up on screen quickly, to do this, you need to get your Rails application server running. -Rails will create several files for you, including +app/views/home/index.html.erb+. This is the template that will be used to display the results of the +index+ action (method) in the +home+ controller. Open this file in your text editor and edit it to contain a single line of code: +h4. Before we begin -<code class="html"> -<h1>Hello, Rails!</h1> -</code> +As an added help, you can find all the code of this application in a ready to run Git repository at "http://github.com/mikel/getting-started-code":http://github.com/mikel/getting-started-code. h4. Starting up the Web Server @@ -283,34 +287,51 @@ This will fire up an instance of the Mongrel web server by default (Rails can al TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. In development mode, Rails does not generally require you to stop the server; changes you make in files will be automatically picked up by the server. -The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. To view the page you just created, navigate to +http://localhost:3000/home/index+. +The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the +About your application’s environment+ link to see a summary of your Application's environment. + +h4. Say "Hello", Rails + +To get Rails saying "Hello", you need to create at minimum a controller and a view. Fortunately, you can do that in a single command. Enter this command in your terminal: + +<shell> +$ script/generate controller home index +</shell> + +TIP: If you're on Windows, or your Ruby is set up in some non-standard fashion, you may need to explicitly pass Rails +script+ commands to Ruby: +ruby script/generate controller home index+. + +Rails will create several files for you, including +app/views/home/index.html.erb+. This is the template that will be used to display the results of the +index+ action (method) in the +home+ controller. Open this file in your text editor and edit it to contain a single line of code: + +<code class="html"> +<h1>Hello, Rails!</h1> +</code> h4. Setting the Application Home Page -You'd probably like to replace the "Welcome Aboard" page with your own application's home page. The first step to doing this is to delete the default page from your application: +Now that we have made the controller and view, we need to tell Rails when we want "Hello Rails" to show up. In our case, we want it to show up when we navigate to the root URL of our site, +http://127.0.0.1:3000/+, instead of the "Welcome Aboard" smoke test. + +The first step to doing this is to delete the default page from your application: <shell> $ rm public/index.html </shell> -Now, you have to tell Rails where your actual home page is located. Open the file +config/routes.rb+ in your editor. This is your application's, _routing file_, which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. At the bottom of the file you'll see the _default routes_: +We need to do this as Rails will deliver any static file in the +public+ directory in preference to any dynamic contact we generate from the controllers. -<ruby> -map.connect ':controller/:action/:id' -map.connect ':controller/:action/:id.:format' -</ruby> +Now, you have to tell Rails where your actual home page is located. Open the file +config/routes.rb+ in your editor. This is your application's, _routing file_, which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. There are only comments in this file, so we need to add at the top the following: -The default routes handle simple requests such as +/home/index+: Rails translates that into a call to the +index+ action in the +home+ controller. As another example, +/posts/edit/1+ would run the +edit+ action in the +posts+ controller with an +id+ of 1. +<ruby> +Blog::Application.routes.draw do |map| -To hook up your home page, you need to add another line to the routing file, above the default routes: + root :to => "home#index" -<ruby> -map.root :controller => "home" + # The priority is based upon order of creation: + # first created -> highest priority. + #... </ruby> -This line illustrates one tiny bit of the "convention over configuration" approach: if you don't specify an action, Rails assumes the +index+ action. +The +root :to => "home#index"+ tells Rails to map the root action to the home controller's index action. -Now if you navigate to +http://localhost:3000+ in your browser, you'll see the +home/index+ view. +Now if you navigate to +http://localhost:3000+ in your browser, you'll see +Hello, Rails!+. NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing.html. @@ -330,28 +351,29 @@ NOTE. While scaffolding will get you up and running quickly, the "one size fits The scaffold generator will build 14 files in your application, along with some folders, and edit one more. Here's a quick overview of what it creates: -|_.File |_.Purpose| -|app/models/post.rb |The Post model| -|db/migrate/20090113124235_create_posts.rb |Migration to create the posts table in your database (your name will include a different timestamp)| -|app/views/posts/index.html.erb |A view to display an index of all posts | -|app/views/posts/show.html.erb |A view to display a single post| -|app/views/posts/new.html.erb |A view to create a new post| -|app/views/posts/edit.html.erb |A view to edit an existing post| -|app/views/layouts/posts.html.erb |A view to control the overall look and feel of the other posts views| -|public/stylesheets/scaffold.css |Cascading style sheet to make the scaffolded views look better| -|app/controllers/posts_controller.rb |The Posts controller| -|test/functional/posts_controller_test.rb |Functional testing harness for the posts controller| -|app/helpers/posts_helper.rb |Helper functions to be used from the posts views| -|config/routes.rb |Edited to include routing information for posts| -|test/fixtures/posts.yml |Dummy posts for use in testing| -|test/unit/post_test.rb |Unit testing harness for the posts model| -|test/unit/helpers/posts_helper_test.rb |Unit testing harness for the posts helper| +|_.File |_.Purpose| +|app/models/post.rb |The Post model| +|db/migrate/20100123083454_create_posts.rb |Migration to create the posts table in your database (your name will include a different timestamp)| +|app/views/posts/index.html.erb| |A view to display an index of all posts | +|app/views/posts/edit.html.erb |A view to edit an existing post| +|app/views/posts/show.html.erb |A view to display a single post| +|app/views/posts/new.html.erb |A view to create a new post| +|app/views/posts/_form.html.erb |A view to control the overall look and feel of the other posts views| +|app/views/layouts/posts.html.erb |A view to control the overall look and feel of the other posts views| +|public/stylesheets/scaffold.css |Cascading style sheet to make the scaffolded views look better| +|app/controllers/posts_controller.rb |The Posts controller| +|test/functional/posts_controller_test.rb |Functional testing harness for the posts controller| +|app/helpers/posts_helper.rb |Helper functions to be used from the posts views| +|config/routes.rb |Edited to include routing information for posts| +|test/fixtures/posts.yml |Dummy posts for use in testing| +|test/unit/post_test.rb |Unit testing harness for the posts model| +|test/unit/helpers/posts_helper_test.rb |Unit testing harness for the posts helper| h4. Running a Migration One of the products of the +script/generate scaffold+ command is a _database migration_. Migrations are Ruby classes that are designed to make it simple to create and modify database tables. Rails uses rake commands to run migrations, and it's possible to undo a migration after it's been applied to your database. Migration filenames include a timestamp to ensure that they're processed in the order that they were created. -If you look in the +db/migrate/20090113124235_create_posts.rb+ file (remember, yours will have a slightly different name), here's what you'll find: +If you look in the +db/migrate/20100123083454_create_posts.rb+ file (remember, yours will have a slightly different name), here's what you'll find: <ruby> class CreatePosts < ActiveRecord::Migration @@ -381,6 +403,15 @@ $ rake db:migrate Remember, you can't run migrations before running +rake db:create+ to create your database, as we covered earlier. +Rails will execute this migration command and tell you is created the Posts table. + +<shell> +== CreatePosts: migrating ================================================= +-- create_table(:posts) + -> 0.0019s +== CreatePosts: migrated (0.0020s) ======================================== +</shell> + NOTE. Because you're working in the development environment by default, this command will apply to the database defined in the +development+ section of your +config/database.yml+ file. h4. Adding a Link @@ -423,8 +454,9 @@ Rails includes methods to help you validate the data that you send to models. Op <ruby> class Post < ActiveRecord::Base - validates_presence_of :name, :title - validates_length_of :title, :minimum => 5 + validates :name, :presence => true + validates :title, :presence => true, + :length => { :minimum => 5 } end </ruby> @@ -447,10 +479,9 @@ created_at: nil, updated_at: nil> >> p.save => false >> p.errors -=> #<ActiveRecord::Errors:0x23bcf0c @base=#<Post id: nil, name: nil, -title: nil, content: "A new post", created_at: nil, updated_at: nil>, -@errors={"name"=>["can't be blank"], "title"=>["can't be blank", -"is too short (minimum is 5 characters)"]}> +=> #<OrderedHash {:title=>["can't be blank", + "is too short (minimum is 5 characters)"], + :name=>["can't be blank"]}> </shell> This code shows creating a new +Post+ instance, attempting to save it and getting +false+ for a return value (indicating that the save failed), and inspecting the +errors+ of the post. @@ -472,7 +503,7 @@ def index end </ruby> -This code sets the +@posts+ instance variable to an array of all posts in the database. +Post.find(:all)+ or +Post.all+ calls the +Post+ model to return all of the posts that are currently in the database, with no limiting conditions. +This code sets the +@posts+ instance variable to an array of all posts in the database. +Post.all+ calls the +Post+ model to return all of the posts that are currently in the database, with no limiting conditions. TIP: For more information on finding records with Active Record, see "Active Record Query Interface":active_record_querying.html. @@ -486,17 +517,19 @@ The +respond_to+ block handles both HTML and XML calls to this action. If you br <th>Name</th> <th>Title</th> <th>Content</th> + <th></th> + <th></th> + <th></th> </tr> <% @posts.each do |post| %> <tr> - <td><%=h post.name %></td> - <td><%=h post.title %></td> - <td><%=h post.content %></td> + <td><%= post.name %></td> + <td><%= post.title %></td> + <td><%= post.content %></td> <td><%= link_to 'Show', post %></td> <td><%= link_to 'Edit', edit_post_path(post) %></td> - <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', - :method => :delete %></td> + <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td> </tr> <% end %> </table> @@ -508,10 +541,11 @@ The +respond_to+ block handles both HTML and XML calls to this action. If you br This view iterates over the contents of the +@posts+ array to display content and links. A few things to note in the view: -* +h+ is a Rails helper method to sanitize displayed data, preventing cross-site scripting attacks * +link_to+ builds a hyperlink to a particular destination * +edit_post_path+ is a helper that Rails provides as part of RESTful routing. You'll see a variety of these helpers for the different actions that the controller includes. +NOTE. In previous versions of Rails, you had to use +<%=h post.name %>+ so that any HTML would be escaped before being inserted into the page. In Rails 3.0, this is now the default. To get unescaped HTML, you now use +<%= raw post.name %>+. + TIP: For more details on the rendering process, see "Layouts and Rendering in Rails":layouts_and_rendering.html. h4. Customizing the Layout @@ -519,21 +553,17 @@ h4. Customizing the Layout The view is only part of the story of how HTML is displayed in your web browser. Rails also has the concept of +layouts+, which are containers for views. When Rails renders a view to the browser, it does so by putting the view's HTML into a layout's HTML. The +script/generate scaffold+ command automatically created a default layout, +app/views/layouts/posts.html.erb+, for the posts. Open this layout in your editor and modify the +body+ tag: <erb> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<!DOCTYPE html> +<html> <head> - <meta http-equiv="content-type" - content="text/html;charset=UTF-8" /> <title>Posts: <%= controller.action_name %></title> <%= stylesheet_link_tag 'scaffold' %> </head> <body style="background: #EEEEEE;"> -<p style="color: green"><%= flash[:notice] %></p> +<p class="notice"><%= notice %></p> -<%= yield %> +<%= yield %> </body> </html> @@ -561,34 +591,48 @@ The +new.html.erb+ view displays this empty Post to the user: <erb> <h1>New post</h1> +<%= render 'form' %> + +<%= link_to 'Back', posts_path %> +</erb> + +The +<%= render 'form' %>+ line is our first introduction to _partials_ in Rails. A partial is a snippet of HTML and Ruby code that can be reused in multiple locations. In this case, the form used to make a new post, is very similar to a form used to edit a post, both have text fields for the name and title and a text area for the content with a button to make a new post or update the existing post. + +If you take a look at +views/posts/_form.html.erb+ file, you will see the following: + +<erb> <% form_for(@post) do |f| %> <%= f.error_messages %> - <p> + <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name %> - </p> - <p> + </div> + <div class="field"> <%= f.label :title %><br /> <%= f.text_field :title %> - </p> - <p> + </div> + <div class="field"> <%= f.label :content %><br /> <%= f.text_area :content %> - </p> - <p> - <%= f.submit "Create" %> - </p> + </div> + <div class="actions"> + <%= f.submit %> + </div> <% end %> - -<%= link_to 'Back', posts_path %> </erb> +This partial receives all the instance variables defined in the calling view file, so in this case, the controller assigned the new Post object to +@post+ and so, this is available in both the view and partial as +@post+. + +For more information on partials, refer to the "Layouts and Rendering in Rails":layouts_and_rendering.html#using-partials guide. + The +form_for+ block is used to create an HTML form. Within this block, you have access to methods to build various controls on the form. For example, +f.text_field :name+ tells Rails to create a text input on the form, and to hook it up to the +name+ attribute of the instance being displayed. You can only use these methods with attributes of the model that the form is based on (in this case +name+, +title+, and +content+). Rails uses +form_for+ in preference to having you write raw HTML because the code is more succinct, and because it explicitly ties the form to a particular model instance. +The +form_for+ block is also smart enough to work out if you are doing a _New Post_ or an _Edit Post_ action, and will set the form +action+ tags and submit button names appropriately in the HTML output. + TIP: If you need to create an HTML form that displays arbitrary fields, not tied to a model, you should use the +form_tag+ method, which provides shortcuts for building forms that are not necessarily tied to a model instance. -When the user clicks the +Create+ button on this form, the browser will send information back to the +create+ method of the controller (Rails knows to call the +create+ method because the form is sent with an HTTP POST request; that's one of the conventions that I mentioned earlier): +When the user clicks the +Create Post+ button on this form, the browser will send information back to the +create+ method of the controller (Rails knows to call the +create+ method because the form is sent with an HTTP POST request; that's one of the conventions that I mentioned earlier): <ruby> def create @@ -596,22 +640,24 @@ def create respond_to do |format| if @post.save - flash[:notice] = 'Post was successfully created.' - format.html { redirect_to(@post) } - format.xml { render :xml => @post, :status => :created, - :location => @post } + format.html { redirect_to(@post, + :notice => 'Post was successfully created.') } + format.xml { render :xml => @post, + :status => :created, :location => @post } else format.html { render :action => "new" } format.xml { render :xml => @post.errors, - :status => :unprocessable_entity } + :status => :unprocessable_entity } end end end </ruby> -The +create+ action instantiates a new Post object from the data supplied by the user on the form, which Rails makes available in the +params+ hash. After saving the new post, it uses +flash[:notice]+ to create an informational message for the user, and redirects to the show action for the post. If there's any problem, the +create+ action just shows the +new+ view a second time, with any error messages. +The +create+ action instantiates a new Post object from the data supplied by the user on the form, which Rails makes available in the +params+ hash. After successfully saving the new post, returns the appropriate format that the user has requested (HTML in our case). It then redirects the user to the resulting post +show+ action and sets a notice to the user that the Post was successfully created. -Rails provides the +flash+ hash (usually just called the Flash) so that messages can be carried over to another action, providing the user with useful information on the status of their request. In the case of +create+, the user never actually sees any page rendered during the Post creation process, because it immediately redirects to the new Post as soon Rails saves the record. The Flash carries over a message to the next action, so that when the user is redirected back to the +show+ action, they are presented with a message saying "Post was successfully created." +If the post was not successfully saved, due to a validation error, then the controller returns the user back to the +new+ action with any error messages so that the user has the chance to fix the error and try again. + +The "Post was successfully created" message is stored inside of the Rails +flash+ hash, (usually just called the Flash) so that messages can be carried over to another action, providing the user with useful information on the status of their request. In the case of +create+, the user never actually sees any page rendered during the Post creation process, because it immediately redirects to the new Post as soon Rails saves the record. The Flash carries over a message to the next action, so that when the user is redirected back to the +show+ action, they are presented with a message saying "Post was successfully created." h4. Showing an Individual Post @@ -633,17 +679,17 @@ The +show+ action uses +Post.find+ to search for a single record in the database <erb> <p> <b>Name:</b> - <%=h @post.name %> + <%= @post.name %> </p> <p> <b>Title:</b> - <%=h @post.title %> + <%= @post.title %> </p> <p> <b>Content:</b> - <%=h @post.content %> + <%= @post.content %> </p> @@ -666,30 +712,15 @@ After finding the requested post, Rails uses the +edit.html.erb+ view to display <erb> <h1>Editing post</h1> -<% form_for(@post) do |f| %> - <%= f.error_messages %> - - <p> - <%= f.label :name %><br /> - <%= f.text_field :name %> - </p> - <p> - <%= f.label :title %><br /> - <%= f.text_field :title %> - </p> - <p> - <%= f.label :content %><br /> - <%= f.text_area :content %> - </p> - <p> - <%= f.submit "Update" %> - </p> -<% end %> +<%= render 'form' %> <%= link_to 'Show', @post %> | <%= link_to 'Back', posts_path %> +<% end %> </erb> +Again, as with the +new+ action, the +edit+ action is using the +form+ partial, this time however, the form will do a PUT action to the PostsController and the submit button will display "Update Post" + Submitting the form created by this view will invoke the +update+ action within the controller: <ruby> @@ -698,13 +729,13 @@ def update respond_to do |format| if @post.update_attributes(params[:post]) - flash[:notice] = 'Post was successfully updated.' - format.html { redirect_to(@post) } + format.html { redirect_to(@post, + :notice => 'Post was successfully updated.') } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @post.errors, - :status => :unprocessable_entity } + :status => :unprocessable_entity } end end end @@ -712,8 +743,6 @@ end In the +update+ action, Rails first uses the +:id+ parameter passed back from the edit view to locate the database record that's being edited. The +update_attributes+ call then takes the rest of the parameters from the request and applies them to this record. If all goes well, the user is redirected to the post's +show+ view. If there are any problems, it's back to +edit+ to correct them. -NOTE. Sharp-eyed readers will have noticed that the +form_for+ declaration is identical for the +new+ and +edit+ views. Rails generates different code for the two forms because it's smart enough to notice that in the one case it's being passed a new record that has never been saved, and in the other case an existing record that has already been saved to the database. In a production Rails application, you would ordinarily eliminate this duplication by moving identical code to a _partial template_, which you could then include in both parent templates. But the scaffold generator tries not to make too many assumptions, and generates code that's easy to modify if you want different forms for +create+ and +edit+. - h4. Destroying a Post Finally, clicking one of the +destroy+ links sends the associated id to the +destroy+ action: @@ -734,60 +763,7 @@ The +destroy+ method of an Active Record model instance removes the correspondin h3. DRYing up the Code -At this point, it's worth looking at some of the tools that Rails provides to eliminate duplication in your code. In particular, you can use _partials_ to clean up duplication in views and _filters_ to help with duplication in controllers. - -h4. Using Partials to Eliminate View Duplication - -As you saw earlier, the scaffold-generated views for the +new+ and +edit+ actions are largely identical. You can pull the shared code out into a partial template. This requires editing the new and edit views, and adding a new template. The new +_form.html.erb+ template should be saved in the same +app/views/posts+ folder as the files from which it is being extracted. Note that the name of this file begins with an underscore; that's the Rails naming convention for partial templates. - -<tt>new.html.erb</tt>: - -<erb> -<h1>New post</h1> - -<%= render :partial => "form" %> - -<%= link_to 'Back', posts_path %> -</erb> - -<tt>edit.html.erb</tt>: - -<erb> -<h1>Editing post</h1> - -<%= render :partial => "form" %> - -<%= link_to 'Show', @post %> | -<%= link_to 'Back', posts_path %> -</erb> - -<tt>_form.html.erb</tt>: - -<erb> -<% form_for(@post) do |f| %> - <%= f.error_messages %> - - <p> - <%= f.label :name %><br /> - <%= f.text_field :name %> - </p> - <p> - <%= f.label :title, "title" %><br /> - <%= f.text_field :title %> - </p> - <p> - <%= f.label :content %><br /> - <%= f.text_area :content %> - </p> - <p> - <%= f.submit "Save" %> - </p> -<% end %> -</erb> - -Now, when Rails renders the +new+ or +edit+ view, it will insert the +_form+ partial at the indicated point. Note the naming convention for partials: if you refer to a partial named +form+ inside of a view, the corresponding file is +_form.html.erb+, with a leading underscore. - -For more information on partials, refer to the "Layouts and Rendering in Rails":layouts_and_rendering.html#using-partials guide. +At this point, it's worth looking at some of the tools that Rails provides to eliminate duplication in your code. In particular, you can use _partials_ to clean up duplication in views (as you saw above with the +new+ and +edit+ views both sharing the +form+ partial) and _filters_ to help with duplication in controllers. h4. Using Filters to Eliminate Controller Duplication @@ -859,14 +835,13 @@ h4. Generating a Model Models in Rails use a singular name, and their corresponding database tables use a plural name. For the model to hold comments, the convention is to use the name Comment. Even if you don't want to use the entire apparatus set up by scaffolding, most Rails developers still use generators to make things like models and controllers. To create the new model, run this command in your terminal: <shell> -$ script/generate model Comment commenter:string body:text - post:references +$ script/generate model Comment commenter:string body:text post:references </shell> This command will generate four files: * +app/models/comment.rb+ - The model -* +db/migrate/20091013214407_create_comments.rb+ - The migration +* +db/migrate/20100124023310_create_comments.rb+ - The migration * +test/unit/comment_test.rb+ and +test/fixtures/comments.yml+ - The test harness. First, take a look at +comment.rb+: @@ -905,7 +880,14 @@ The +t.references+ line sets up a foreign key column for the association between $ rake db:migrate </shell> -Rails is smart enough to only execute the migrations that have not already been run against the current database. +Rails is smart enough to only execute the migrations that have not already been run against the current database, so in this case you will just see: + +<shell> +== CreateComments: migrating ================================================= +-- create_table(:comments) + -> 0.0019s +== CreateComments: migrated (0.0020s) ======================================== +</shell> h4. Associating Models @@ -926,8 +908,9 @@ You'll need to edit the +post.rb+ file to add the other side of the association: <ruby> class Post < ActiveRecord::Base - validates_presence_of :name, :title - validates_length_of :title, :minimum => 5 + validates :name, :presence => true + validates :title, :presence => true, + :length => { :minimum => 5 } has_many :comments end </ruby> @@ -936,12 +919,14 @@ These two declarations enable a good bit of automatic behavior. For example, if TIP: For more information on Active Record associations, see the "Active Record Associations":association_basics.html guide. -h4. Adding a Route +h4. Adding a Route for Comments -_Routes_ are entries in the +config/routes.rb+ file that tell Rails how to match incoming HTTP requests to controller actions. Open up that file and find the existing line referring to +posts+ (it will be right at the top of the file). Then edit it as follows: +As with the +home+ controller, we will need to add a route so that Rails knows where we would like to navigate to see +comments+. Open up the +config/routes.rb+ file again, you will see an entry that was added automatically for +posts+ near the top by the scaffold generator, +resources :posts+, edit it as follows: <ruby> -map.resources :posts, :has_many => :comments +resources :posts do + resources :comments +end </ruby> This creates +comments+ as a _nested resource_ within +posts+. This is another part of capturing the hierarchical relationship that exists between posts and comments. @@ -1063,20 +1048,19 @@ The +views/comments/index.html.erb+ view: <tr> <th>Commenter</th> <th>Body</th> + <th></th> + <th></th> + <th></th> </tr> -<% for comment in @comments %> +<% @comments.each do |comment| %> <tr> - <td><%=h comment.commenter %></td> - <td><%=h comment.body %></td> + <td><%= comment.commenter %></td> + <td><%= comment.body %></td> <td><%= link_to 'Show', post_comment_path(@post, comment) %></td> - <td> - <%= link_to 'Edit', edit_post_comment_path(@post, comment) %> - </td> - <td> - <%= link_to 'Destroy', post_comment_path(@post, comment), - :confirm => 'Are you sure?', :method => :delete %> - </td> + <td><%= link_to 'Edit', edit_post_comment_path(@post, comment) %></td> + <td><%= link_to 'Destroy', post_comment_path(@post, comment), + :confirm => 'Are you sure?', :method => :delete %></td> </tr> <% end %> </table> @@ -1087,28 +1071,45 @@ The +views/comments/index.html.erb+ view: <%= link_to 'Back to Post', @post %> </erb> -The +views/comments/new.html.erb+ view: +The +views/comments/new.html.erb+ view (again using a partial to render a form that is shared with the +edit+ view): <erb> <h1>New comment</h1> +<%= render 'form' %> + +<%= link_to 'Back', post_comments_path(@post) %> +</erb> + +The +views/comments/edit.html.erb+ view: + +<erb> +<h1>Editing comment</h1> + +<%= render 'form' %> + +<%= link_to 'Show', post_comment_path(@post, @comment) %> | +<%= link_to 'Back', post_comments_path(@post) %> +</erb> + +The +views/comments/_form.html.erb+ partial: + +<erb> <% form_for([@post, @comment]) do |f| %> <%= f.error_messages %> - <p> + <div class="field"> <%= f.label :commenter %><br /> <%= f.text_field :commenter %> - </p> - <p> + </div> + <div class="field"> <%= f.label :body %><br /> <%= f.text_area :body %> - </p> - <p> - <%= f.submit "Create" %> - </p> + </div> + <div class="actions"> + <%= f.submit %> + </div> <% end %> - -<%= link_to 'Back', post_comments_path(@post) %> </erb> The +views/comments/show.html.erb+ view: @@ -1118,43 +1119,18 @@ The +views/comments/show.html.erb+ view: <p> <b>Commenter:</b> - <%=h @comment.commenter %> + <%= @comment.commenter %> </p> <p> <b>Comment:</b> - <%=h @comment.body %> + <%= @comment.body %> </p> <%= link_to 'Edit', edit_post_comment_path(@post, @comment) %> | <%= link_to 'Back', post_comments_path(@post) %> </erb> -The +views/comments/edit.html.erb+ view: - -<erb> -<h1>Editing comment</h1> - -<% form_for([@post, @comment]) do |f| %> - <%= f.error_messages %> - - <p> - <%= f.label :commenter %><br /> - <%= f.text_field :commenter %> - </p> - <p> - <%= f.label :body %><br /> - <%= f.text_area :body %> - </p> - <p> - <%= f.submit "Update" %> - </p> -<% end %> - -<%= link_to 'Show', post_comment_path(@post, @comment) %> | -<%= link_to 'Back', post_comments_path(@post) %> -</erb> - Again, the added complexity here (compared to the views you saw for managing posts) comes from the necessity of juggling a post and its comments at the same time. h4. Hooking Comments to Posts @@ -1164,29 +1140,29 @@ As a next step, I'll modify the +views/posts/show.html.erb+ view to show the com <erb> <p> <b>Name:</b> - <%=h @post.name %> + <%= @post.name %> </p> <p> <b>Title:</b> - <%=h @post.title %> + <%= @post.title %> </p> <p> <b>Content:</b> - <%=h @post.content %> + <%= @post.content %> </p> <h2>Comments</h2> -<% @post.comments.each do |c| %> +<% @post.comments.each do |comment| %> <p> <b>Commenter:</b> - <%=h c.commenter %> + <%= comment.commenter %> </p> <p> <b>Comment:</b> - <%=h c.body %> + <%= comment.body %> </p> <% end %> @@ -1203,21 +1179,22 @@ If you decide at some point to delete a post, you likely want to delete the comm <ruby> class Post < ActiveRecord::Base - validates_presence_of :name, :title - validates_length_of :title, :minimum => 5 + validates :name, :presence => true + validates :title, :presence => true, + :length => { :minimum => 5 } has_many :comments, :dependent => :destroy end </ruby> h3. Building a Multi-Model Form -Comments and posts are edited on two separate forms - which makes sense, given the flow of this mini-application. But what if you want to edit more than one thing on a single form? Rails 2.3 offers new support for nested forms. Let's add support for giving each post multiple tags, right in the form where you create the post. First, create a new model to hold the tags: +Comments and posts are edited on two separate forms - which makes sense, given the flow of this mini-application. But what if you want to edit more than one thing on a single form? Rails offers support for nested forms. Let's add support for giving each post multiple tags, right in the form where you create the post. First, create a new model to hold the tags: <shell> $ script/generate model tag name:string post:references </shell> -Run the migration to create the database table: +Again, run the migration to create the database table: <shell> $ rake db:migrate @@ -1227,8 +1204,9 @@ Next, edit the +post.rb+ file to create the other side of the association, and t <ruby> class Post < ActiveRecord::Base - validates_presence_of :name, :title - validates_length_of :title, :minimum => 5 + validates :name, :presence => true + validates :title, :presence => true, + :length => { :minimum => 5 } has_many :comments has_many :tags @@ -1242,51 +1220,53 @@ The +:allow_destroy+ option on the nested attribute declaration tells Rails to d You'll also need to modify +views/posts/_form.html.erb+ to include the tags: <erb> -<% @post.tags.build if @post.tags.empty? %> +<% @post.tags.build %> <% form_for(@post) do |post_form| %> <%= post_form.error_messages %> - <p> + <div class="field"> <%= post_form.label :name %><br /> <%= post_form.text_field :name %> - </p> - <p> - <%= post_form.label :title, "Title" %><br /> + </div> + <div class="field"> + <%= post_form.label :title %><br /> <%= post_form.text_field :title %> - </p> - <p> + </div> + <div class="field"> <%= post_form.label :content %><br /> <%= post_form.text_area :content %> - </p> + </div> <h2>Tags</h2> <% post_form.fields_for :tags do |tag_form| %> - <p> + <div class="field"> <%= tag_form.label :name, 'Tag:' %> <%= tag_form.text_field :name %> - </p> + </div> <% unless tag_form.object.nil? || tag_form.object.new_record? %> - <p> - <%= tag_form.label :_delete, 'Remove:' %> - <%= tag_form.check_box :_delete %> - </p> + <div class="field"> + <%= tag_form.label :_destroy, 'Remove:' %> + <%= tag_form.check_box :_destroy %> + </div> <% end %> <% end %> - <p> - <%= post_form.submit "Save" %> - </p> + <div class="actions"> + <%= post_form.submit %> + </div> <% end %> </erb> +You will note that we also have changed the +form_for(@post) do |f|+ to +form_for(@post) do |post_form|+ and changed all the +f+ method calls as well to show more clearly what is going on. + With these changes in place, you'll find that you can edit a post and its tags directly on the same view. -NOTE. You may want to use JavaScript to dynamically add additional tags on a single form. For an example of this and other advanced techniques, see the "complex form examples application":http://github.com/alloy/complex-form-examples/tree/master. +NOTE. You may want to use JavaScript to dynamically add additional tags on a single form. For an example of this and other advanced techniques, see the "complex form examples application":http://github.com/mikel/complex-form-examples/. h3. What's Next? Now that you've seen your first Rails application, you should feel free to update it and experiment on your own. But you don't have to do everything without help. As you need assistance getting up and running with Rails, feel free to consult these support resources: -* The "Ruby On Rails guides":http://guides.rubyonrails.org +* The "Ruby On Rails guides":http://guides.rails.info * The "Ruby on Rails mailing list":http://groups.google.com/group/rubyonrails-talk * The "#rubyonrails":irc://irc.freenode.net/#rubyonrails channel on irc.freenode.net * The "Rails Wiki":http://wiki.rubyonrails.org/ @@ -1300,6 +1280,7 @@ h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/2 +* January 24, 2010: Re-write for Rails 3.0 by "Mikel Lindsaar":credits:html#raasdnil * July 18, 2009: Minor cleanup in anticipation of Rails 2.3.3 by "Mike Gunderloy":credits.html#mgunderloy * February 1, 2009: Updated for Rails 2.3 by "Mike Gunderloy":credits.html#mgunderloy * November 3, 2008: Formatting patch from Dave Rothlisberger @@ -1307,4 +1288,4 @@ h3. Changelog * October 16, 2008: Revised based on feedback from Pratik Naik by "Mike Gunderloy":credits.html#mgunderloy (not yet approved for publication) * October 13, 2008: First complete draft by "Mike Gunderloy":credits.html#mgunderloy (not yet approved for publication) * October 12, 2008: More detail, rearrangement, editing by "Mike Gunderloy":credits.html#mgunderloy (not yet approved for publication) -* September 8, 2008: initial version by James Miller (not yet approved for publication) +* September 8, 2008: initial version by "James Miller":credits.html#bensie (not yet approved for publication) diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile index 0cee413ac3..2cb98e9ee6 100644 --- a/railties/guides/source/layouts_and_rendering.textile +++ b/railties/guides/source/layouts_and_rendering.textile @@ -27,21 +27,74 @@ I'll cover each of these methods in turn. But first, a few words about the very h4. Rendering by Default: Convention Over Configuration in Action -You've heard that Rails promotes "convention over configuration." Default rendering is an excellent example of this. By default, controllers in Rails automatically render views with names that correspond to actions. For example, if you have this code in your +BooksController+ class: +You've heard that Rails promotes "convention over configuration." Default rendering is an excellent example of this. By default, controllers in Rails automatically render views with names that correspond to valid routes. For example, if you have this code in your +BooksController+ class: <ruby> -def show - @book = Book.find(params[:id]) +class BooksController < ApplicationController +end +</ruby> + +And the following in your routes file: + +<ruby> +resources :books +</ruby> + +And you have a view file +app/views/books/index.html.erb+: + +<ruby> +<h1>Books are coming soon!</h1> +</ruby> + +Rails will automatically render +app/views/books/index.html.erb+ when you navigate to +/books+ and you will see on your screen that "Books are coming soon!" + +However a coming soon screen is only minimally useful, so you will soon create your +Book+ model and add the index action to +BooksController+: + +<ruby> +class BooksController < ApplicationController + def index + @books = Book.all + end end </ruby> -Rails will automatically render +app/views/books/show.html.erb+ after running the method. In fact, if you have the default catch-all route in place (+map.connect ':controller/:action/:id'+), Rails will even render views that don't have any code at all in the controller. For example, if you have the default route in place and a request comes in for +/books/sale_list+, Rails will render +app/views/books/sale_list.html.erb+ in response. +Note that again, we have convention over configuration, in that there is no explicit render at the end of this index action. The rule is that if you do not explicitly render something by the end of the controller action, rails will look for the +action_name.html.erb+ template in the controllers view path and then render that, so in this case, Rails will render the +app/views/books/index.html.erb+ file. -NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), +.rjs+ for RJS (javascript with embedded ruby) and +.builder+ for Builder (XML generator). You'll also find +.rhtml+ used for ERB templates and +.rxml+ for Builder templates, but those extensions are now formally deprecated and will be removed from a future version of Rails. +So in our view, we want to display the properties of all the books, we could do this with an ERB template like this: + +<ruby> +<h1>Listing Books</h1> + +<table> + <tr> + <th>Title</th> + <th>Summary</th> + <th></th> + <th></th> + <th></th> + </tr> + +<% @books.each do |book| %> + <tr> + <td><%= book.title %></td> + <td><%= book.content %></td> + <td><%= link_to 'Show', book %></td> + <td><%= link_to 'Edit', edit_book_path(book) %></td> + <td><%= link_to 'Remove', book, :confirm => 'Are you sure?', :method => :delete %></td> + </tr> +<% end %> +</table> + +<br /> + +<%= link_to 'New book', new_book_path %> +</ruby> + +NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), +.rjs+ for RJS (javascript with embedded ruby) and +.builder+ for Builder (XML generator). h4. Using +render+ -In most cases, the +ActionController::Base#render+ method does the heavy lifting of rendering your application's content for use by a browser. There are a variety of ways to customize the behavior of +render+. You can render the default view for a Rails template, or a specific template, or a file, or inline code, or nothing at all. You can render text, JSON, or XML. You can specify the content type or HTTP status of the rendered response as well. +In most cases, the +ActionController::Base#render+ method does the heavy lifting of rendering your application's content for use by a browser. There are a variety of ways to customise the behaviour of +render+. You can render the default view for a Rails template, or a specific template, or a file, or inline code, or nothing at all. You can render text, JSON, or XML. You can specify the content type or HTTP status of the rendered response as well. TIP: If you want to see the exact results of a call to +render+ without needing to inspect it in a browser, you can call +render_to_string+. This method takes exactly the same options as +render+, but it returns a string instead of sending a response back to the browser. @@ -53,7 +106,24 @@ Perhaps the simplest thing you can do with +render+ is to render nothing at all: render :nothing => true </ruby> -This will send an empty response to the browser (though it will include any status headers you set with the :status option, discussed below). +If you look at the response for this using Curl you will see the following: + +<shell> + $ curl -i 127.0.0.1:3000/books +HTTP/1.1 200 OK +Connection: close +Date: Sun, 24 Jan 2010 09:25:18 GMT +Transfer-Encoding: chunked +Content-Type: */*; charset=utf-8 +X-Runtime: 0.014297 +Set-Cookie: _blog_session=...snip...; path=/; HttpOnly +Cache-Control: no-cache + + + $ +</shell> + +We see there is an empty response (no data after the +Cache-Control+ line), but that Rails has set the response to 200 OK, so the request was successful. You can set the +:status+ options on render to change this response. Rendering nothing can be useful for AJAX requests where all you want to send back to the browser is an acknowledgement that the request was completed. TIP: You should probably be using the +head+ method, discussed later in this guide, instead of +render :nothing+. This provides additional flexibility and makes it explicit that you're only generating HTTP headers. @@ -73,7 +143,7 @@ def update end </ruby> -If the call to +update_attributes+ fails, calling the +update+ action in this controller will render the +edit.html.erb+ template belonging to the same controller. +If the call to +update_attributes+ fails, calling the +update+ action in this controller will render the +edit.html.erb+ template belonging to the same controller. If you prefer, you can use a symbol instead of a string to specify the action to render: @@ -89,7 +159,7 @@ def update end </ruby> -To be explicit, you can use +render+ with the +:action+ option (though this is no longer necessary as of Rails 2.3): +To be explicit, you can use +render+ with the +:action+ option (though this is no longer necessary in Rails 3.0): <ruby> def update @@ -140,6 +210,31 @@ NOTE: By default, the file is rendered without using the current layout. If you TIP: If you're running on Microsoft Windows, you should use the +:file+ option to render a file, because Windows filenames do not have the same format as Unix filenames. +h5. Wrapping it up + +The above three methods of render (rendering another template within the controller, rendering a template within another controller and rendering an arbitrary file on the file system) are actually all variants of the same action. + +In fact, in the BooksController method, inside of the edit action where we want to render the edit template if the book does not update successfully, all of the following render calls would all render the +edit.html.erb+ template in the +views/books+ directory: + +<ruby> +render :edit +render :action => :edit +render 'edit' +render 'edit.html.erb' +render :action => 'edit' +render :action => 'edit.html.erb' +render 'books/edit' +render 'books/edit.html.erb' +render :template => 'books/edit' +render :template => 'books/edit.html.erb' +render '/path/to/rails/app/views/books/edit' +render '/path/to/rails/app/views/books/edit.html.erb' +render :file => '/path/to/rails/app/views/books/edit' +render :file => '/path/to/rails/app/views/books/edit.html.erb' +</ruby> + +Which one you use is really a matter of style and convention, but the rule of thumb is to use the simplest one that makes sense for the code you are writing. + h5. Using +render+ with +:inline+ The +render+ method can do without a view completely, if you're willing to use the +:inline+ option to supply ERB as part of the method call. This is perfectly valid: @@ -180,7 +275,7 @@ render :text => "OK" TIP: Rendering pure text is most useful when you're responding to AJAX or web service requests that are expecting something other than proper HTML. -NOTE: By default, if you use the +:text+ option, the file is rendered without using the current layout. If you want Rails to put the text into the current layout, you need to add the +:layout => true+ option +NOTE: By default, if you use the +:text+ option the text is rendered without using the current layout. If you want Rails to put the text into the current layout, you need to add the +:layout => true+ option. h5. Rendering JSON @@ -314,21 +409,21 @@ end Now, if the current user is a special user, they'll get a special layout when viewing a product. You can even use an inline method to determine the layout: +You can also decide the layout by passing a Proc object, the block you give the Proc will be given the +controller+ instance, so you can make decisions based on the current request. For example: + <ruby> class ProductsController < ApplicationController - layout proc { |controller| controller.request.xhr? ? 'popup' : 'application' } - # ... + layout Proc.new { |controller| controller.request.xhr? ? 'popup' : 'application' } end </ruby> h6. Conditional Layouts -Layouts specified at the controller level support +:only+ and +:except+ options that take either a method name or an array of method names: +Layouts specified at the controller level support +:only+ and +:except+ options that take either a method name or an array of method names which correspond to method names within the controller: <ruby> class ProductsController < ApplicationController layout "product", :except => [:index, :rss] - #... end </ruby> @@ -343,7 +438,6 @@ Layouts are shared downwards in the hierarchy, and more specific layouts always <ruby> class ApplicationController < ActionController::Base layout "main" - #... end </ruby> @@ -351,7 +445,6 @@ end <ruby> class PostsController < ApplicationController - # ... end </ruby> @@ -360,7 +453,6 @@ end <ruby> class SpecialPostsController < PostsController layout "special" - # ... end </ruby> @@ -418,6 +510,8 @@ def show end </ruby> +Make sure you use +and return+ and not +&& return+ because while the former will work, the latter will not due to operator precedence in the Ruby Language. + Note that the implicit render done by ActionController detects if +render+ has been called, and thus avoids this error. Therefore, the following will work without errors: <ruby> @@ -463,11 +557,11 @@ Consider these actions to see the difference: <ruby> def index - @books = Book.find(:all) + @books = Book.all end def show - @book = Book.find(params[:id]) + @book = Book.find_by_id(params[:id]) if @book.nil? render :action => "index" end @@ -478,19 +572,39 @@ With the code in this form, there will be likely be a problem if the +@book+ var <ruby> def index - @books = Book.find(:all) + @books = Book.all end def show - @book = Book.find(params[:id]) + @book = Book.find_by_id(params[:id]) if @book.nil? - redirect_to :action => "index" + redirect_to :action => :index end end </ruby> With this code, the browser will make a fresh request for the index page, the code in the +index+ method will run, and all will be well. +The only downside to this code, is that it requires a round trip to the browser, the browser requested the show action with +/books/1+ and the controller finds that there are no books, so the controller sends out a 301 redirect response to the browser telling it to go to +/books/+, the browser complies and sends a new request back to the controller asking now for the +index+ action, the controller then gets all the books in the database and renders the index template, sending it back down to the browser which then shows it on your screen. + +While in a small app, this added latency might not be a problem, it is something to think about when speed of response is of the essence. One way to handle this double request (though a contrived example) could be: + +<ruby> +def index + @books = Book.all +end + +def show + @book = Book.find_by_id(params[:id]) + if @book.nil? + @books = Book.all + render "index", :alert => 'Your book was not found!' + end +end +</ruby> + +Which would detect that there are no books populate the +@books+ instance variable with all the books in the database and then directly render the +index.html.erb+ template returning it to the browser with a flash alert message telling the user what happened. + h4. Using +head+ To Build Header-Only Responses The +head+ method exists to let you send back responses to the browser that have only headers. It provides a more obvious alternative to calling +render :nothing+. The +head+ method takes one response, which is interpreted as a hash of header names and values. For example, you can return only an error header: @@ -499,12 +613,39 @@ The +head+ method exists to let you send back responses to the browser that have head :bad_request </ruby> +Which would produce the following header: + +<shell> +HTTP/1.1 400 Bad Request +Connection: close +Date: Sun, 24 Jan 2010 12:15:53 GMT +Transfer-Encoding: chunked +Content-Type: text/html; charset=utf-8 +X-Runtime: 0.013483 +Set-Cookie: _blog_session=...snip...; path=/; HttpOnly +Cache-Control: no-cache +</shell> + Or you can use other HTTP headers to convey additional information: <ruby> head :created, :location => photo_path(@photo) </ruby> +Which would produce: + +<shell> +HTTP/1.1 201 Created +Connection: close +Date: Sun, 24 Jan 2010 12:16:44 GMT +Transfer-Encoding: chunked +Location: /photos/1 +Content-Type: text/html; charset=utf-8 +X-Runtime: 0.083496 +Set-Cookie: _blog_session=...snip...; path=/; HttpOnly +Cache-Control: no-cache +</shell> + h3. Structuring Layouts When Rails renders a view as a response, it does so by combining the view with the current layout (using the rules for finding the current layout that were covered earlier in this guide). Within a layout, you have access to three tools for combining different bits of output to form the overall response: @@ -517,12 +658,14 @@ I'll discuss each of these in turn. h4. Asset Tags -Asset tags provide methods for generating HTML that links views to assets like images, javascript, stylesheets, and feeds. There are four types of include tag: +Asset tags provide methods for generating HTML that links views to assets like images, videos, audio, javascript, stylesheets, and feeds. There are six types of include tag: * +auto_discovery_link_tag+ * +javascript_include_tag+ * +stylesheet_link_tag+ * +image_tag+ +* +video_tag+ +* +audio_tag+ You can use these tags in layouts or other views, although the tags other than +image_tag+ are most commonly used in the +<head>+ section of a layout. @@ -663,10 +806,10 @@ You can even use dynamic paths such as +cache/#{current_site}/main/display+. h5. Linking to Images with +image_tag+ -The +image_tag+ helper builds an HTML +<image>+ tag to the specified file. By default, files are loaded from +public/images+. If you don't specify an extension, +.png+ is assumed by default: +The +image_tag+ helper builds an HTML +<image />+ tag to the specified file. By default, files are loaded from +public/images+, note, you must specify the extension, previous versions of Rails would allow you to just call the image name and would append +.png+ if no extension was given, Rails 3.0 does not. <erb> -<%= image_tag "header" %> +<%= image_tag "header.png" %> </erb> You can supply a path to the image if you like: @@ -678,14 +821,93 @@ You can supply a path to the image if you like: You can supply a hash of additional HTML options: <erb> -<%= image_tag "icons/delete.gif", :height => 45 %> +<%= image_tag "icons/delete.gif", {:height => 45} %> +</erb> + +You can also supply an alternate image to show on mouseover: + +<erb> +<%= image_tag "home.gif", :onmouseover => "menu/home_highlight.gif" %> +</erb> + +Or alternate text if the user has rendering images turned off in their browser, if you do not specify an explicit alt tag, it defaults to the file name of the file, capitalized and with no extension, for example, these two image tags would return the same code: + +<erb> +<%= image_tag "home.gif" %> +<%= image_tag "home.gif", :alt => "Home" %> +</erb> + +You can also specify a special size tag, in the format "{width}x{height}": + +<erb> +<%= image_tag "home.gif", :size => "50x20" %> +</erb> + +In addition to the above special tags, you can supply a final hash of standard HTML options, such as +:class+ or +:id+ or +:name+: + +<erb> +<%= image_tag "home.gif", :alt => "Go Home", + :id => "HomeImage", + :class => 'nav_bar' %> +</erb> + +h5. Linking to Videos with +video_tag+ + +The +video_tag+ helper builds an HTML 5 +<video>+ tag to the specified file. By default, files are loaded from +public/videos+. + +<erb> +<%= video_tag "movie.ogg" %> +</erb> + +Produces + +<erb> +<video src="/videos/movie.ogg" /> </erb> -There are also three special options you can use with +image_tag+: +Like an +image_tag+ you can supply a path, either absolute, or relative to the +public/videos+ directory. Additionally you can specify the +:size => "#{width}x#{height}"+ option just like an +image_tag+. Video tags can also have any of the HTML options specified at the end (+id+, +class+ et al). -* +:alt+ specifies the alt text for the image (which defaults to the file name of the file, capitalized and with no extension) -* +:size+ specifies both width and height, in the format "{width}x{height}" (for example, "150x125") -* +:mouseover+ sets an alternate image to be used when the onmouseover event is fired. +The video tag also supports all of the +<video>+ HTML options through the HTML options hash, including: + +* +:poster => 'image_name.png'+, provides an image to put in place of the video before it starts playing. +* +:autoplay => true+, starts playing the video on page load. +* +:loop => true+, loops the video once it gets to the end. +* +:controls => true+, provides browser supplied controls for the user to interact with the video. +* +:autobuffer => true+, the video will pre load the file for the user on page load. + +You can also specify multiple videos to play by passing an array of videos to the +video_tag+: + +<erb> +<%= video_tag ["trailer.ogg", "movie.ogg"] %> +</erb> + +This will produce: + +<erb> +<video><source src="trailer.ogg" /><source src="movie.ogg" /></video> +</erb> + +h5. Linking to Audio files with +audio_tag+ + +The +audio_tag+ helper builds an HTML 5 +<audio>+ tag to the specified file. By default, files are loaded from +public/audios+. + +<erb> +<%= audio_tag "music.mp3" %> +</erb> + +You can supply a path to the image if you like: + +<erb> +<%= image_tag "music/first_song.mp3" %> +</erb> + +You can also supply a hash of additional options, such as +:id+, +:class+ etc. + +Like the +video_tag+, the +audio_tag+ has special options: + +* +:autoplay => true+, starts playing the audio on page load +* +:controls => true+, provides browser supplied controls for the user to interact with the audio. +* +:autobuffer => true+, the audio will pre load the file for the user on page load. h4. Understanding +yield+ @@ -752,13 +974,13 @@ h5. Naming Partials To render a partial as part of a view, you use the +render+ method within the view, and include the +:partial+ option: <ruby> -<%= render :partial => "menu" %> +<%= render "menu" %> </ruby> This will render a file named +_menu.html.erb+ at that point within the view being rendered. Note the leading underscore character: partials are named with a leading underscore to distinguish them from regular views, even though they are referred to without the underscore. This holds true even when you're pulling in a partial from another folder: <ruby> -<%= render :partial => "shared/menu" %> +<%= render "shared/menu" %> </ruby> That code will pull in the partial from +app/views/shared/_menu.html.erb+. @@ -768,14 +990,14 @@ h5. Using Partials to Simplify Views One way to use partials is to treat them as the equivalent of subroutines: as a way to move details out of a view so that you can grasp what's going on more easily. For example, you might have a view that looked like this: <erb> -<%= render :partial => "shared/ad_banner" %> +<%= render "shared/ad_banner" %> <h1>Products</h1> <p>Here are a few of our fine products:</p> ... -<%= render :partial => "shared/footer" %> +<%= render "shared/footer" %> </erb> Here, the +_ad_banner.html.erb+ and +_footer.html.erb+ partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page. @@ -787,7 +1009,7 @@ h5. Partial Layouts A partial can use its own layout file, just as a view can use a layout. For example, you might call a partial like this: <erb> -<%= render :partial => "link_area", :layout => "graybar" %> +<%= render "link_area", :layout => "graybar" %> </erb> This would look for a partial named +_link_area.html.erb+ and render it using the layout +_graybar.html.erb+. Note that layouts for partials follow the same leading-underscore naming as regular partials, and are placed in the same folder with the partial that they belong to (not in the master +layouts+ folder). @@ -801,8 +1023,7 @@ You can also pass local variables into partials, making them even more powerful <erb> <h1>New zone</h1> <%= error_messages_for :zone %> -<%= render :partial => "form", :locals => - { :button_label => "Create zone", :zone => @zone } %> +<%= render :partial => "form", :locals => { :zone => @zone } %> </erb> * +edit.html.erb+ @@ -810,8 +1031,7 @@ You can also pass local variables into partials, making them even more powerful <erb> <h1>Editing zone</h1> <%= error_messages_for :zone %> -<%= render :partial => "form", :locals => - { :button_label => "Update zone", :zone => @zone } %> +<%= render :partial => "form", :locals => { :zone => @zone } %> </erb> * +_form.html.erb+ @@ -823,12 +1043,12 @@ You can also pass local variables into partials, making them even more powerful <%= f.text_field :name %> </p> <p> - <%= f.submit button_label %> + <%= f.submit %> </p> <% end %> </erb> -Although the same partial will be rendered into both views, the label on the submit button is controlled by a local variable passed into the partial. +Although the same partial will be rendered into both views, Action View's submit helper will return "Create Zone" for the new action and "Update Zone" for the edit action. Every partial also has a local variable with the same name as the partial (minus the underscore). You can pass an object in to this local variable via the +:object+ option: @@ -838,15 +1058,15 @@ Every partial also has a local variable with the same name as the partial (minus Within the +customer+ partial, the +customer+ variable will refer to +@new_customer+ from the parent view. -WARNING: In previous versions of Rails, the default local variable would look for an instance variable with the same name as the partial in the parent. This behavior is deprecated in Rails 2.2 and will be removed in a future version. +WARNING: In previous versions of Rails, the default local variable would look for an instance variable with the same name as the partial in the parent. This behavior was deprecated in 2.3 and has been removed in Rails 3.0. If you have an instance of a model to render into a partial, you can use a shorthand syntax: <erb> -<%= render :partial => @customer %> +<%= render @customer %> </erb> -Assuming that the +@customer+ instance variable contains an instance of the +Customer+ model, this will use +_customer.html.erb+ to render it. +Assuming that the +@customer+ instance variable contains an instance of the +Customer+ model, this will use +_customer.html.erb+ to render it and will pass the local variable +customer+ into the partial which will refer to the +@customer+ instance variable in the parent view. h5. Rendering Collections @@ -865,63 +1085,70 @@ Partials are very useful in rendering collections. When you pass a collection to <p>Product Name: <%= product.name %></p> </erb> -When a partial is called with a pluralized collection, then the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is +_product+, and within the +_product+ partial, you can refer to +product+ to get the instance that is being rendered. To use a custom local variable name within the partial, specify the +:as+ option in the call to the partial: +When a partial is called with a pluralized collection, then the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is +_product+, and within the +_product+ partial, you can refer to +product+ to get the instance that is being rendered. + +In Rails 3.0 there is also a shorthand for this, assuming +@posts+ is a collection of +post+ instances, you can simply do in the +index.html.erb+: <erb> -<%= render :partial => "product", :collection => @products, :as => :item %> +<h1>Products</h1> +<%= render @products %> </erb> -With this change, you can access an instance of the +@products+ collection as the +item+ local variable within the partial. +To produce the same result. -TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by +_counter+. For example, if you're rendering +@products+, within the partial you can refer to +product_counter+ to tell you how many times the partial has been rendered. +Rails determines the name of the partial to use by looking at the model name in the collection. In fact, you can even create a heterogeneous collection and render it this way, and Rails will choose the proper partial for each member of the collection: -You can also specify a second partial to be rendered between instances of the main partial by using the +:spacer_template+ option: +* +index.html.erb+ <erb> -<%= render :partial => "product", :collection => @products, - :spacer_template => "product_ruler" %> +<h1>Contacts</h1> +<%= render [customer1, employee1, customer2, employee2] %> </erb> -Rails will render the +_product_ruler+ partial (with no data passed in to it) between each pair of +_product+ partials. - -There's also a shorthand syntax available for rendering collections. For example, if +@products+ is a collection of products, you can render the collection this way: - -* +index.html.erb+ +* +customers/_customer.html.erb+ <erb> -<h1>Products</h1> -<%= render :partial => @products %> +<p>Customer: <%= customer.name %></p> </erb> -* +_product.html.erb+ +* +employees/_employee.html.erb+ <erb> -<p>Product Name: <%= product.name %></p> +<p>Employee: <%= employee.name %></p> </erb> -Rails determines the name of the partial to use by looking at the model name in the collection. In fact, you can even create a heterogeneous collection and render it this way, and Rails will choose the proper partial for each member of the collection: +In this case, Rails will use the customer or employee partials as appropriate for each member of the collection. -* +index.html.erb+ +h5. Local Variables + +To use a custom local variable name within the partial, specify the +:as+ option in the call to the partial: <erb> -<h1>Contacts</h1> -<%= render :partial => - [customer1, employee1, customer2, employee2] %> +<%= render :partial => "product", :collection => @products, :as => :item %> </erb> -* +_customer.html.erb+ +With this change, you can access an instance of the +@products+ collection as the +item+ local variable within the partial. + +You can also pass in arbitrary local variables to any partial you are rendering with the +:locals => {}+ option: <erb> -<p>Name: <%= customer.name %></p> +<%= render :partial => 'products', :collection => @products, + :as => :item, :locals => {:title => "Products Page"} %> </erb> -* +_employee.html.erb+ +Would render a partial +_products.html.erb+ once for each instance of +product+ in the +@products+ instance variable passing the instance to the partial as a local variable called +item+ and to each partial, make the local variable +title+ available with the value +Products Page+. + +TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by +_counter+. For example, if you're rendering +@products+, within the partial you can refer to +product_counter+ to tell you how many times the partial has been rendered. This does not work in conjunction with the +:as => :value+ option. + +You can also specify a second partial to be rendered between instances of the main partial by using the +:spacer_template+ option: + +h5. Spacer Templates <erb> -<p>Name: <%= employee.name %></p> +<%= render @products, :spacer_template => "product_ruler" %> </erb> -In this case, Rails will use the customer or employee partials as appropriate for each member of the collection. +Rails will render the +_product_ruler+ partial (with no data passed in to it) between each pair of +_product+ partials. h4. Using Nested Layouts @@ -964,12 +1191,13 @@ On pages generated by +NewsController+, you want to hide the top menu and add a That's it. The News views will use the new layout, hiding the top menu and adding a new right menu inside the "content" div. -There are several ways of getting similar results with different sub-templating schemes using this technique. Note that there is no limit in nesting levels. One can use the +ActionView::render+ method via +render :file => 'layouts/news'+ to base a new layout on the News layout. If one is sure she will not subtemplate the +News+ layout, she can omit the +yield(:news_content) or + part. +There are several ways of getting similar results with different sub-templating schemes using this technique. Note that there is no limit in nesting levels. One can use the +ActionView::render+ method via +render :file => 'layouts/news'+ to base a new layout on the News layout. If you are sure you will not subtemplate the +News+ layout, you can replace the +yield(:news_content) or yield+ with simply +yield+. h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/15 +* January 25, 2010: Rails 3.0 Update by "Mikel Lindsaar":credits.html#raasdnil * December 27, 2008: Merge patch from Rodrigo Rosenfeld Rosas covering subtemplates * December 27, 2008: Information on new rendering defaults by "Mike Gunderloy":credits.html#mgunderloy * November 9, 2008: Added partial collection counter by "Mike Gunderloy":credits.html#mgunderloy diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index c038d0ac70..2dc2ba1002 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -2,6 +2,114 @@ require 'rails/initializable' require 'rails/configuration' module Rails + # Railtie is the core of the Rails Framework and provides all the hooks and + # methods you need to link your plugin into Rails. + # + # What Railtie does is make every component of Rails a "plugin" and creates + # an API that exposes all the powers that the builtin components need + # to any plugin author. + # + # In fact, every major component of Rails (Action Mailer, Action Controller, + # Action View, Active Record and Active Resource) are all now just plain + # old plugins, so anything they can do, your plugin can do. + # + # Developing a plugin for Rails does not _require_ any implementation of + # Railtie, there is no fixed rule, but as a guideline, if your plugin works + # by just being required before Rails boots, then there is no need for you + # to hook into Railtie, but if you need to interact with the Rails framework + # during boot, or after boot, then Railtie is what you need to do that + # interaction. + # + # For example, the following would need you to implement Railtie in your + # plugin: + # + # * creating initializers (including route insertion) + # * modifying the render path (think HAML et al) + # * adding Rails config.* keys to the environment + # * setting up a subscriber to the Rails +ActiveSupport::Notifications+ + # * adding global Rake tasks into rails + # * setting up a default configuration for the Application + # + # Railtie gives you a central place to connect into the Rails framework. If you + # find yourself writing plugin code that is having to monkey patch parts of the + # Rails framework to achieve something, there is probably a better, more elegant + # way to do it through Railtie, if there isn't, then you have found a lacking + # feature of Railtie, please lodge a ticket. + # + # Implementing Railtie in your plugin is by creating a class Railtie in your + # application that has your plugin name and making sure that this gets loaded + # durng boot time of the Rails stack. + # + # You can do this however you wish, but three straight forward ways are: + # + # == For gems or plugins that are not used outside of Rails + # + # * Create a Railtie subclass within your lib/my_plugin.rb file: + # + # # lib/my_plugin.rb + # module MyPlugin + # class Railtie < Rails::Railtie + # end + # end + # + # * Pass in your plugin name + # + # # lib/my_plugin.rb + # module MyPlugin + # class Railtie < Rails::Railtie + # plugin_name :my_plugin + # end + # end + # + # == For gems that could be used without Rails + # + # * Create a file (say, lib/my_gem/railtie.rb) which contains class Railtie inheriting from + # Rails::Railtie and is namespaced to your gem: + # + # # lib/my_gem/railtie.rb + # module MyGem + # class Railtie < Rails::Railtie + # end + # end + # + # * Require your own gem as well as rails in this file: + # + # # lib/my_gem/railtie.rb + # require 'my_gem' + # require 'rails' + # + # module MyGem + # class Railtie < Rails::Railtie + # end + # end + # + # * Give your gem a unique name: + # + # # lib/my_gem/railtie.rb + # require 'my_gem' + # require 'rails' + # + # module MyGem + # class Railtie < Rails::Railtie + # plugin_name :my_gem + # end + # end + # + # * Make sure your Gem loads the railtie.rb file if Rails is loaded first, an easy + # way to check is by checking for the Rails constant which will exist if Rails + # has started: + # + # # lib/my_gem.rb + # module MyGem + # require 'lib/railtie' if defined?(Rails) + # end + # + # * Or instead of doing the require automatically, you can ask your users to require + # it for you in their Gemfile: + # + # # #{USER_RAILS_ROOT}/Gemfile + # gem "my_gem", :require_as => ["my_gem", "my_gem/railtie"] + # class Railtie autoload :Configurable, "rails/railtie/configurable" autoload :Configuration, "rails/railtie/configuration" diff --git a/railties/lib/rails/subscriber.rb b/railties/lib/rails/subscriber.rb index 8c62f562d9..6638ff28c1 100644 --- a/railties/lib/rails/subscriber.rb +++ b/railties/lib/rails/subscriber.rb @@ -3,10 +3,10 @@ require 'active_support/notifications' module Rails # Rails::Subscriber is an object set to consume ActiveSupport::Notifications - # on initialization with solely purpose of logging. The subscriber dispatches - # notifications to a regirested object based on its given namespace. + # on initialization with the sole purpose of logging. The subscriber dispatches + # notifications to a registered object based on it's given namespace. # - # An example would be ActiveRecord subscriber responsible for logging queries: + # An example would be an Active Record subscriber responsible for logging queries: # # module ActiveRecord # class Railtie @@ -18,16 +18,16 @@ module Rails # end # end # - # It's finally registed as: + # Which would be registed as: # # Rails::Subscriber.add :active_record, ActiveRecord::Railtie::Subscriber.new # - # So whenever a "active_record.sql" notification arrive to Rails::Subscriber, + # So whenever an +active_record.sql+ notification arrives to Rails::Subscriber, # it will properly dispatch the event (ActiveSupport::Notifications::Event) to # the sql method. # - # This is useful because it avoids spanning several subscribers just for logging - # purposes(which slows down the main thread). Besides of providing a centralized + # This avoids spanning several subscribers just for logging purposes + # (which slows down the main thread). It also provides a centralized # facility on top of Rails.logger. # # Subscriber also has some helpers to deal with logging and automatically flushes @@ -97,7 +97,6 @@ module Rails # option is set to true, it also adds bold to the string. This is based # on Highline implementation and it automatically appends CLEAR to the end # of the returned String. - # def color(text, color, bold=false) return text unless colorize_logging color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol) |