From a07d0f87863e01ef931c87bd35bd36c564c20cd3 Mon Sep 17 00:00:00 2001 From: Mikel Lindsaar Date: Mon, 1 Feb 2010 10:08:20 +1100 Subject: Full update on ActiveModel documentation --- activemodel/README | 211 +++++++++++++++++++--- activemodel/lib/active_model/attribute_methods.rb | 140 ++++++++++++-- activemodel/lib/active_model/errors.rb | 107 +++++++++++ activemodel/lib/active_model/lint.rb | 16 +- activemodel/lib/active_model/naming.rb | 15 ++ activemodel/lib/active_model/observing.rb | 23 ++- activemodel/lib/active_model/serialization.rb | 57 ++++++ activemodel/lib/active_model/translation.rb | 19 +- activemodel/lib/active_model/validations.rb | 39 +++- activemodel/lib/active_model/validator.rb | 5 +- 10 files changed, 562 insertions(+), 70 deletions(-) diff --git a/activemodel/README b/activemodel/README index 7c9c754a8e..0d6fd1f21c 100644 --- a/activemodel/README +++ b/activemodel/README @@ -1,45 +1,68 @@ = Active Model - defined interfaces for Rails - + Prior to Rails 3.0, if a plugin or gem developer wanted to be able to have an object interact with Action Pack helpers, it was required to either copy chunks of code from Rails, or monkey patch entire helpers to make them handle objects that did not look like Active Record. This generated code duplication and fragile applications that broke on upgrades. - + Active Model is a solution for this problem. - + 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 - - class MyClass - 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. + +* Adding attribute magic to your objects + + 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 + + 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... person = Person.new @@ -52,6 +75,142 @@ functionality from the following modules: person.name = 'robert' person.save person.previous_changes # => {'name' => ['bob, 'robert']} - + {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 # => "\n :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] diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 977a101277..200a6afbf0 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -5,6 +5,48 @@ module ActiveModel class MissingAttributeError < NoMethodError end + # ActiveModel::AttributeMethods 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: + # + # * include ActiveModel::AttributeMethods in your object + # * Call each Attribute Method module method you want to add, such as + # attribute_method_suffix or attribute_method_prefix + # * Call define_attribute_methods 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 @@ -22,12 +64,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 +111,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 +149,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 +187,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 +220,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 +266,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) } @@ -175,6 +274,7 @@ module ActiveModel @attribute_methods_generated = nil end + # Returns true if the attribute methods defined have been generated. def generated_attribute_methods #:nodoc: @generated_attribute_methods ||= begin mod = Module.new diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index ff11ddc605..d8320275df 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -2,9 +2,66 @@ 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 +70,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 +82,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: + # + # # + # # + # # name can't be blank + # # name must be specified + # # def to_xml(options={}) require 'builder' unless defined? ::Builder options[:root] ||= "errors" @@ -59,6 +165,7 @@ module ActiveModel # for the same attribute and ensure that this error object returns false when asked if empty?. More than one # error can be added to the same +attribute+ in which case an array will be returned on a call to on(attribute). # 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..7bf0ad712d 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 valid? # # 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 new_record? # # 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,12 +54,11 @@ 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. - + # # Returns an Array of Strings that are the errors for the attribute in # question. If localization is used, the Strings should be localized # for the current locale. If no error is present, this method should diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 4cd68a0c89..89e8f8b1ea 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -35,6 +35,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. 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..1c48d4613a 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -2,6 +2,63 @@ 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 # => "\n {"name"=>"Bob"} + # person.to_json # => "{\"name\":\"Bob\"}" + # person.to_xml # => "\n "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 + # #=> #["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 -- cgit v1.2.3 From 65ad16568c60dd1a4f5c65d2c97ead7a4c0fb5d5 Mon Sep 17 00:00:00 2001 From: Mikel Lindsaar Date: Mon, 1 Feb 2010 10:10:53 +1100 Subject: Updating copyright dates on all licenses --- actionmailer/MIT-LICENSE | 2 +- actionmailer/lib/action_mailer.rb | 2 +- actionpack/MIT-LICENSE | 2 +- actionpack/lib/action_dispatch.rb | 2 +- actionpack/lib/action_pack.rb | 2 +- actionpack/lib/action_view.rb | 2 +- activemodel/MIT-LICENSE | 2 +- activemodel/lib/active_model.rb | 2 +- activerecord/MIT-LICENSE | 2 +- activerecord/lib/active_record.rb | 2 +- activeresource/MIT-LICENSE | 2 +- activesupport/MIT-LICENSE | 2 +- activesupport/lib/active_support/core_ext/string/interpolation.rb | 2 +- railties/MIT-LICENSE | 2 +- railties/lib/rails/dispatcher.rb | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE index e7accc5ea1..a345a2419d 100644 --- a/actionmailer/MIT-LICENSE +++ b/actionmailer/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2009 David Heinemeier Hansson +Copyright (c) 2004-2010 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 0265e6e222..7f5bcad922 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2009 David Heinemeier Hansson +# Copyright (c) 2004-2010 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE index e7accc5ea1..a345a2419d 100644 --- a/actionpack/MIT-LICENSE +++ b/actionpack/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2009 David Heinemeier Hansson +Copyright (c) 2004-2010 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index c6eb097ee5..479ea959e6 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2009 David Heinemeier Hansson +# Copyright (c) 2004-2010 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb index b90f89be39..1a1497385a 100644 --- a/actionpack/lib/action_pack.rb +++ b/actionpack/lib/action_pack.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2009 David Heinemeier Hansson +# Copyright (c) 2004-2010 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 93aa69c060..66fc530bec 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2009 David Heinemeier Hansson +# Copyright (c) 2004-2010 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE index e7accc5ea1..a345a2419d 100644 --- a/activemodel/MIT-LICENSE +++ b/activemodel/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2009 David Heinemeier Hansson +Copyright (c) 2004-2010 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 1609075f7e..026430fee3 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2009 David Heinemeier Hansson +# Copyright (c) 2004-2010 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE index e6df48772f..86bcb23b7c 100644 --- a/activerecord/MIT-LICENSE +++ b/activerecord/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2009 David Heinemeier Hansson +Copyright (c) 2004-2010 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 0c4cc6a554..9535c576be 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2009 David Heinemeier Hansson +# Copyright (c) 2004-2010 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/activeresource/MIT-LICENSE b/activeresource/MIT-LICENSE index 5eb8c94758..1bf965ff1e 100644 --- a/activeresource/MIT-LICENSE +++ b/activeresource/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2006-2009 David Heinemeier Hansson +Copyright (c) 2006-2010 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE index d6fdf21596..cd928b856d 100644 --- a/activesupport/MIT-LICENSE +++ b/activesupport/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2005-2009 David Heinemeier Hansson +Copyright (c) 2005-2010 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activesupport/lib/active_support/core_ext/string/interpolation.rb b/activesupport/lib/active_support/core_ext/string/interpolation.rb index 2048d35091..06d3505c60 100644 --- a/activesupport/lib/active_support/core_ext/string/interpolation.rb +++ b/activesupport/lib/active_support/core_ext/string/interpolation.rb @@ -1,7 +1,7 @@ =begin heavily based on Masao Mutoh's gettext String interpolation extension http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb - Copyright (C) 2005-2009 Masao Mutoh + Copyright (C) 2005-2010 Masao Mutoh You may redistribute it and/or modify it under the same license terms as Ruby. =end diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE index e6df48772f..86bcb23b7c 100644 --- a/railties/MIT-LICENSE +++ b/railties/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2009 David Heinemeier Hansson +Copyright (c) 2004-2010 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/railties/lib/rails/dispatcher.rb b/railties/lib/rails/dispatcher.rb index 5d383eacd1..75ee4de140 100644 --- a/railties/lib/rails/dispatcher.rb +++ b/railties/lib/rails/dispatcher.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2009 David Heinemeier Hansson +# Copyright (c) 2004-2010 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the -- cgit v1.2.3 From 65fb2c7b323a78c383cb023fc6dca3adf893a93d Mon Sep 17 00:00:00 2001 From: Mikel Lindsaar Date: Mon, 1 Feb 2010 14:57:54 +1100 Subject: Completely updated the ActionMailer guide --- .../guides/source/action_mailer_basics.textile | 373 +++++++++++---------- 1 file changed, 189 insertions(+), 184 deletions(-) diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile index 931ebe8a34..a8e310f6ec 100644 --- a/railties/guides/source/action_mailer_basics.textile +++ b/railties/guides/source/action_mailer_basics.textile @@ -1,12 +1,12 @@ h2. Action Mailer Basics -This guide should provide you with all you need to get started in sending and receiving emails from/to your application, and many internals of Action Mailer. It also covers how to test your mailers. +This guide should provide you with all you need to get started in sending and receiving emails from and to your application, and many internals of Action Mailer. It also covers how to test your mailers. endprologue. h3. Introduction -Action Mailer allows you to send emails from your application using a mailer model and views. So, in Rails, emails are used by creating models that inherit from +ActionMailer::Base+ that live alongside other models in +app/models+. Those models have associated views that appear alongside controller views in +app/views+. +Action Mailer allows you to send emails from your application using a mailer model and views. So, in Rails, emails are used by creating mailers that inherit from +ActionMailer::Base+ and live in +app/mailers+. Those mailers have associated views that appear alongside controller views in +app/views+. h3. Sending Emails @@ -18,22 +18,22 @@ h5. Create the Mailer ./script/generate mailer UserMailer -exists app/models/ -create app/views/user_mailer -exists test/unit/ -create test/fixtures/user_mailer -create app/models/user_mailer.rb -create test/unit/user_mailer_test.rb +create app/mailers/user_mailer.rb +invoke erb +create app/views/user_mailer +invoke test_unit +create test/functional/user_mailer_test.rb -So we got the model, the fixtures, and the tests. +So we got the mailer, the fixtures, and the tests. -h5. Edit the Model +h5. Edit the Mailer -+app/models/user_mailer.rb+ contains an empty mailer: ++app/mailers/user_mailer.rb+ contains an empty mailer: class UserMailer < ActionMailer::Base + default :from => "from@example.com" end @@ -41,153 +41,215 @@ Let's add a method called +welcome_email+, that will send an email to the user's class UserMailer < ActionMailer::Base + default :from => "notifications@example.com" + def welcome_email(user) - recipients user.email - from "My Awesome Site Notifications " - subject "Welcome to My Awesome Site" - sent_on Time.now - body( {:user => user, :url => "http://example.com/login"}) + @user = user + @url = "http://example.com/login" + mail(:to => user.email, + :subject => "Welcome to My Awesome Site") end + end -Here is a quick explanation of the options presented in the preceding method. For a full list of all available options, please have a look further down at the Complete List of ActionMailer user-settable attributes section. +Here is a quick explanation of the items presented in the preceding method. For a full list of all available options, please have a look further down at the Complete List of ActionMailer user-settable attributes section. -|recipients| The recipients of the email. It can be a string or, if there are multiple recipients, an array of strings| -|from| The from address of the email| -|subject| The subject of the email| -|sent_on| The timestamp for the email| +|default Hash| This is a hash of default values for any email you send, in this case we are setting the :from header to a value for all messages in this class, this can be overridden on a per email basis| +|mail| The actual email message, we are passing the :to and :subject headers in| -The keys of the hash passed to +body+ become instance variables in the view. Thus, in our example the mailer view will have a +@user+ and a +@url+ instance variables available. +And instance variables we define in the method become available for use in the view. h5. Create a Mailer View -Create a file called +welcome_email.text.html.erb+ in +app/views/user_mailer/+. This will be the template used for the email, formatted in HTML: +Create a file called +welcome_email.html.erb+ in +app/views/user_mailer/+. This will be the template used for the email, formatted in HTML: - + -

Welcome to example.com, <%=h @user.first_name %>

+

Welcome to example.com, <%= @user.name %>

You have successfully signed up to example.com, and your username is: <%= @user.login %>.
- To login to the site, just follow this link: <%=h @url %>. + To login to the site, just follow this link: <%= @url %>.

Thanks for joining and have a great day!

-Had we wanted to send text-only emails, the file would have been called +welcome_email.text.plain.erb+. Rails sets the content type of the email to be the one in the filename. +It is also a good idea to make a text part for this email, to do this, create a file called +welcome_email.text.erb+ in +app/views/user_mailer/+: + + +Welcome to example.com, <%= @user.name %> +=============================================== + +You have successfully signed up to example.com, and your username is: <%= @user.login %>. + +To login to the site, just follow this link: <%= @url %>. + +Thanks for joining and have a great day! + + +When you call the +mail+ method now, Action Mailer will detect the two templates (text and HTML) and automatically generate a multipart/alternative email. h5. Wire It Up So That the System Sends the Email When a User Signs Up There are three ways to achieve this. One is to send the email from the controller that sends the email, another is to put it in a +before_create+ callback in the user model, and the last one is to use an observer on the user model. Whether you use the second or third methods is up to you, but staying away from the first is recommended. Not because it's wrong, but because it keeps your controller clean, and keeps all logic related to the user model within the user model. This way, whichever way a user is created (from a web form, or from an API call, for example), we are guaranteed that the email will be sent. -Let's see how we would go about wiring it up using an observer: +Let's see how we would go about wiring it up using an observer. + +First off, we need to create a simple +User+ scaffold: + + +$ script/generate scaffold user name:string email:string login:string +$ rake db:migrate + -In +config/environment.rb+: +Now that we have a user model to play with, edit +config/application.rb+ and register the observer: -Rails::Initializer.run do |config| - # ... - config.active_record.observers = :user_observer +module MailerGuideCode + class Application < Rails::Application + # ... + config.active_record.observers = :user_observer + end end -You can place the observer in +app/models+ where it will be loaded automatically by Rails. +You can make a +app/observers+ directory and Rails will automatically load it for you (Rails will automatically load anything in the +app+ directory as of version 3.0) -Now create a file called +user_observer.rb+ in +app/models+ depending on where you stored it, and make it look like: +Now create a file called +user_observer.rb+ in +app/observers+ and make it look like: class UserObserver < ActiveRecord::Observer def after_create(user) - UserMailer.deliver_welcome_email(user) + UserMailer.welcome_email(user).deliver end end -Notice how we call +deliver_welcome_email+? In Action Mailer we send emails by calling +deliver_<method_name>+. In UserMailer, we defined a method called +welcome_email+, and so we deliver the email by calling +deliver_welcome_email+. The next section will go through how Action Mailer achieves this. +Notice how we call UserMailer.welcome_email(user)? Even though in the user_mailer.rb file we defined an instance method, we are calling the method_name +welcome_email(user)+ on the class. This is a peculiarity of Action Mailer. + +Note. In previous versions of Rails, you would call +deliver_welcome_email+ or +create_welcome_email+ however in Rails 3.0 this has been deprecated in favour of just calling the method name itself. + +The method +welcome_email+ returns a Mail::Message object which can then just be told +deliver+ to send itself out. + + +h4. Complete List of Action Mailer Methods -h4. Action Mailer and Dynamic +deliver_<method_name>+ methods +There are just three methods that you need to send pretty much any email message: -So how does Action Mailer understand this +deliver_welcome_email+ call? If you read the documentation (http://api.rubyonrails.org/files/vendor/rails/actionmailer/README.html), you will find this in the "Sending Emails" section: +* headers - Specifies any header on the email you want, you can pass a hash of header field names and value pairs, or you can call headers[:field_name] = 'value' +* attachments - Allows you to add attachments to your email, for example attachments['file-name.jpg'] = File.read('file-name.jpg') +* mail - Sends the actual email itself. You can pass in headers as a hash to the mail method as a parameter, mail will then create an email, either plain text, or multipart, depending on what email templates you have defined. -You never instantiate your mailer class. Rather, your delivery instance methods are automatically wrapped in class methods that start with the word +deliver_+ followed by the name of the mailer method that you would like to deliver. +h5. Custom Headers -So, how exactly does this work? +Defining custom headers are simple, you can do it one of three ways: -Looking at the +ActionMailer::Base+ source, you will find this: +* Defining a header field as a parameter to the +mail+ method: -def method_missing(method_symbol, *parameters)#:nodoc: - case method_symbol.id2name - when /^create_([_a-z]\w*)/ then new($1, *parameters).mail - when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver! - when "new" then nil - else super - end -end +mail(:x_spam => value) + + +* Passing in a key value assignment to the +headers+ method: + + +headers[:x_spam] = value -Hence, if the method name starts with +deliver_+ followed by any combination of lowercase letters or underscore, +method_missing+ calls +new+ on your mailer class (+UserMailer+ in our example above), sending the combination of lower case letters or underscore, along with the parameters. The resulting object is then sent the +deliver!+ method, which well... delivers it. - -h4. Complete List of Action Mailer User-Settable Attributes - -|bcc| The BCC addresses of the email, either as a string (for a single address) or an array of strings (for multiple addresses)| -|body| The body of the email. This is either a hash (in which case it specifies the variables to pass to the template when it is rendered), or a string, in which case it specifies the actual body of the message| -|cc| The CC addresses for the email, either as a string (for a single address) or an array of strings (for multiple addresses)| -|charset| The charset to use for the email. This defaults to the +default_charset+ specified for ActionMailer::Base.| -|content_type| The content type for the email. This defaults to "text/plain" but the filename may specify it| -|from| The from address of the email| -|reply_to| The address (if different than the "from" address) to direct replies to this email| -|headers| Additional headers to be added to the email| -|implicit_parts_order| The order in which parts should be sorted, based on the content type. This defaults to the value of +default_implicit_parts_order+| -|mime_version| Defaults to "1.0", but may be explicitly given if needed| -|recipient| The recipient addresses of the email, either as a string (for a single address) or an array of strings (for multiple addresses)| -|sent_on| The timestamp on which the message was sent. If unset, the header will be set by the delivery agent| -|subject| The subject of the email| -|template| The template to use. This is the "base" template name, without the extension or directory, and may be used to have multiple mailer methods share the same template| +* Passing a hash of key value pairs to the +headers+ method: + + +headers {:x_spam => value, :x_special => another_value} + + +h5. Adding Attachments + +Adding attachments has been simplified in Action Mailer 3.0. + +* Pass the file name and content and Action Mailer and the Mail gem will automatically guess the mime_type, set the encoding and create the attachment. + + +attachments['filename.jpg'] = File.read('/path/to/filename.jpg') + + +Note. Mail will automatically Base64 encode an attachment, if you want something different, pre encode your content and pass in the encoded content and encoding in a +Hash+ to the +attachments+ method. + +* Pass the file name and specify headers and content and Action Mailer and Mail will use the settings you pass in. + + +encoded_content = SpecialEncode(File.read('/path/to/filename.jpg')) +attachments['filename.jpg'] = {:mime_type => 'application/x-gzip', + :encoding => 'SpecialEncoding', + :content => encoded_content } + + +Note. If you specify an encoding, Mail will assume that your content is already encoded and not try to Base64 encode it. h4. Mailer Views -Mailer views are located in the +app/views/name_of_mailer_class+ directory. The specific mailer view is known to the class because it's name is the same as the mailer method. So for example, in our example from above, our mailer view for the +welcome_email+ method will be in +app/views/user_mailer/welcome_email.text.html.erb+ for the HTML version and +welcome_email.text.plain.erb+ for the plain text version. +Mailer views are located in the +app/views/name_of_mailer_class+ directory. The specific mailer view is known to the class because it's name is the same as the mailer method. So for example, in our example from above, our mailer view for the +welcome_email+ method will be in +app/views/user_mailer/welcome_email.html.erb+ for the HTML version and +welcome_email.text.erb+ for the plain text version. To change the default mailer view for your action you do something like: class UserMailer < ActionMailer::Base + default :from => "notifications@example.com" + def welcome_email(user) - recipients user.email - from "My Awesome Site Notifications" - subject "Welcome to My Awesome Site" - sent_on Time.now - body( {:user => user, :url => "http://example.com/login"}) - content_type "text/html" - # use some_other_template.text.(html|plain).erb instead - template "some_other_template" + @user = user + @url = "http://example.com/login" + mail(:to => user.email, + :subject => "Welcome to My Awesome Site") do |format| + format.html { render 'another_template' } + format.text { render 'another_template' } + end end + end +Will render 'another_template.text.erb' and 'another_template.html.erb'. The render command is the same one used inside of Action Controller, so you can use all the same options, such as :text etc. + h4. Action Mailer Layouts -Just like controller views, you can also have mailer layouts. The layout name needs to end in "_mailer" to be automatically recognized by your mailer as a layout. So in our UserMailer example, we need to call our layout +user_mailer.text.(html|plain).erb+. In order to use a different file just use: +Just like controller views, you can also have mailer layouts. The layout name needs to be the same as your mailer, such as +user_mailer.html.erb+ and +user_mailer.text.erb+ to be automatically recognized by your mailer as a layout. + +In order to use a different file just use: class UserMailer < ActionMailer::Base - layout 'awesome' # use awesome.text.(html|plain).erb as the layout + layout 'awesome' # use awesome.(html|text).erb as the layout end Just like with controller views, use +yield+ to render the view inside the layout. +You can also pass in a :layout => 'layout_name' option to the render call inside the format block to specify different layouts for different actions: + + +class UserMailer < ActionMailer::Base + def welcome_email(user) + mail(:to => user.email) do |format| + format.html { render :layout => 'my_layout' } + format.text + end + end +end + + +Will render the HTML part using the my_layout.html.erb file and the text part with the usual user_mailer.text.erb file if it exists. + h4. Generating URLs in Action Mailer Views URLs can be generated in mailer views using +url_for+ or named routes. + Unlike controllers, the mailer instance doesn't have any context about the incoming request so you'll need to provide the +:host+, +:controller+, and +:action+: @@ -197,50 +259,48 @@ Unlike controllers, the mailer instance doesn't have any context about the incom When using named routes you only need to supply the +:host+: -<%= users_url(:host => "example.com") %> +<%= user_url(@user, :host => "example.com") %> Email clients have no web context and so paths have no base URL to form complete web addresses. Thus, when using named routes only the "_url" variant makes sense. -It is also possible to set a default host that will be used in all mailers by setting the +:host+ option in -the +ActionMailer::Base.default_url_options+ hash as follows: +It is also possible to set a default host that will be used in all mailers by setting the +:host+ option in the +ActionMailer::Base.default_url_options+ hash as follows: -ActionMailer::Base.default_url_options[:host] = "example.com" - - -This can also be set as a configuration option in +config/environment.rb+: +class UserMailer < ActionMailer::Base + default_url_options[:host] = "example.com" - -config.action_mailer.default_url_options = { :host => "example.com" } + def welcome_email(user) + @user = user + @url = user_url(@user) + mail(:to => user.email, + :subject => "Welcome to My Awesome Site") + end +end -If you set a default +:host+ for your mailers you need to pass +:only_path => false+ to +url_for+. Otherwise it doesn't get included. - h4. Sending Multipart Emails -Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have +welcome_email.text.plain.erb+ and +welcome_email.text.html.erb+ in +app/views/user_mailer+, Action Mailer will automatically send a multipart email with the HTML and text versions setup as different parts. +Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have +welcome_email.text.erb+ and +welcome_email.html.erb+ in +app/views/user_mailer+, Action Mailer will automatically send a multipart email with the HTML and text versions setup as different parts. -To explicitly specify multipart messages, you can do something like: +The order of the parts getting inserted is determined by the :parts_order inside of the ActionMailer::Base.default method. If you want to explicitly alter the order, you can either change the :parts_order or explicitly render the parts in a different order: class UserMailer < ActionMailer::Base def welcome_email(user) - recipients user.email_address - subject "New account information" - from "system@example.com" - content_type "multipart/alternative" - - part :content_type => "text/html", - :body => "

html content, can also be the name of an action that you call

" - - part "text/plain" do |p| - p.body = "text content, can also be the name of an action that you call" + @user = user + @url = user_url(@user) + mail(:to => user.email, + :subject => "Welcome to My Awesome Site") do |format| + format.html + format.text end end end +Will put the HTML part first, and the plain text part second. + h4. Sending Emails with Attachments Attachments can be added by using the +attachment+ method: @@ -248,53 +308,16 @@ Attachments can be added by using the +attachment+ method: class UserMailer < ActionMailer::Base def welcome_email(user) - recipients user.email_address - subject "New account information" - from "system@example.com" - content_type "multipart/alternative" - - attachment :content_type => "image/jpeg", - :body => File.read("an-image.jpg") - - attachment "application/pdf" do |a| - a.body = generate_your_pdf_here() - end + @user = user + @url = user_url(@user) + attachments['terms.pdf'] = File.read('/path/terms.pdf') + mail(:to => user.email, + :subject => "Please see the Terms and Conditions attached") end end -h4. Sending Multipart Emails with Attachments - -Once you use the +attachment+ method, ActionMailer will no longer automagically use the correct template based on the filename, nor will it properly order the alternative parts. You must declare which template you are using for each content type via the +part+ method. And you must declare these templates in the proper order. - -In the following example, there would be two template files, +welcome_email_html.erb+ and +welcome_email_plain.erb+ in the +app/views/user_mailer+ folder. The +text/plain+ part must be listed first for full compatibility with email clients. If +text/plain+ is listed after +text/html+, some clients may display both the HTML and plain text versions of the email. The text alternatives alone must be enclosed in a +multipart/alternative+ part. Do not set the entire message's +content_type+ to +multipart/alternative+ or some email clients may ignore the display of attachments such as PDF's. - - -class UserMailer < ActionMailer::Base - def welcome_email(user) - recipients user.email_address - subject "New account information" - from "system@example.com" - - part "multipart/alternative" do |pt| - pt.part "text/plain" do |p| - p.body = render_message("welcome_email_plain", :message => "text content") - end - - pt.part "text/html" do |p| - p.body = render_message("welcome_email_html", :message => "

HTML content

") - end - end - - attachment :content_type => "image/jpeg", - :body => File.read("an-image.jpg") - - attachment "application/pdf" do |a| - a.body = generate_your_pdf_here() - end - end -end -
+The above will send a multipart email with an attachment, properly nested with the top level being mixed/multipart and the first part being a mixed/alternative containing the plain text and HTML email messages. h3. Receiving Emails @@ -329,12 +352,7 @@ end h3. Using Action Mailer Helpers -Action Mailer classes have 4 helper methods available to them: - -|add_template_helper(helper_module)|Makes all the (instance) methods in the helper module available to templates rendered through this controller.| -|helper(*args, &block)| Declare a helper: helper :foo requires 'foo_helper' and includes FooHelper in the template class. helper FooHelper includes FooHelper in the template class. helper { def foo() "#{bar} is the very best" end } evaluates the block in the template class, adding method foo. helper(:three, BlindHelper) { def mice() 'mice' end } does all three. | -|helper_method| Declare a controller method as a helper. For example, helper_method :link_to def link_to(name, options) ... end makes the link_to controller method available in the view.| -|helper_attr| Declare a controller attribute as a helper. For example, helper_attr :name attr_accessor :name makes the name and name= controller methods available in the view. The is a convenience wrapper for helper_method.| +Action Mailer now just inherits from Abstract Controller, so you have access to the same generic helpers as you do in Action Controller. h3. Action Mailer Configuration @@ -348,50 +366,36 @@ The following configuration options are best made in one of the environment file |delivery_method|Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test.| |perform_deliveries|Determines whether deliver_* methods are actually carried out. By default they are, but this can be turned off to help functional testing.| |deliveries|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.| -|default_charset|The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also pick a different charset from inside a method with charset.| -|default_content_type|The default content type used for the main part of the message. Defaults to "text/plain". You can also pick a different content type from inside a method with content_type.| -|default_mime_version|The default mime version used for the message. Defaults to 1.0. You can also pick a different value from inside a method with mime_version.| -|default_implicit_parts_order|When a message is built implicitly (i.e. multiple parts are assembled from templates which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client and appear last in the mime encoded message. You can also pick a different order from inside a method with implicit_parts_order.| - h4. Example Action Mailer Configuration -An example would be: +An example would be adding the following to your appropriate config/environments/env.rb file: -ActionMailer::Base.delivery_method = :sendmail -ActionMailer::Base.sendmail_settings = { - :location => '/usr/sbin/sendmail', - :arguments => '-i -t' -} -ActionMailer::Base.perform_deliveries = true -ActionMailer::Base.raise_delivery_errors = true -ActionMailer::Base.default_charset = "iso-8859-1" +config.action_mailer.delivery_method = :sendmail +# Defaults to: +# config.action_mailer.sendmail_settings = { +# :location => '/usr/sbin/sendmail', +# :arguments => '-i -t' +# } +config.action_mailer.perform_deliveries = true +config.action_mailer.raise_delivery_errors = true h4. Action Mailer Configuration for GMail -Instructions copied from "this blog entry":http://www.fromjavatoruby.com/2008/11/actionmailer-with-gmail-must-issue.html by Robert Zotter. - -First you must install the "action_mailer_tls":http://github.com/openrain/action_mailer_tls plugin, then all you have to do is configure Action Mailer: - - -ActionMailer::Base.smtp_settings = { - :address => "smtp.gmail.com", - :port => 587, - :domain => "domain.com", - :user_name => "user@domain.com", - :password => "password", - :authentication => :plain -} - - -h4. Configure Action Mailer to Recognize HAML Templates - -In +config/environment.rb+, add the following line: +As Action Mailer now uses the Mail gem, this becomes as simple as adding to your config/environments/env.rb file: -ActionMailer::Base.register_template_extension('haml') +config.action_mailer.delivery_method = :smtp +config.action_mailer.smtp_settings = { + :address => "smtp.gmail.com", + :port => 587, + :domain => 'baci.lindsaar.net', + :user_name => '', + :password => '', + :authentication => 'plain', + :enable_starttls_auto => true } h3. Mailer Testing @@ -412,7 +416,8 @@ class UserMailerTest < ActionMailer::TestCase # Test the body of the sent email contains what we expect it to assert_equal [user.email], email.to assert_equal "Welcome to My Awesome Site", email.subject - assert_match /Welcome to example.com, #{user.first_name}/, email.body + assert_match /

Welcome to example.com, #{user.name}<\/h1>/, email.encoded + assert_match /Welcome to example.com, #{user.name}/, email.encoded end end -- cgit v1.2.3 From aa9f549965853009affed4a5e656f5f937024f5d Mon Sep 17 00:00:00 2001 From: Mikel Lindsaar Date: Mon, 1 Feb 2010 19:59:45 +1100 Subject: Updates to output and warning on being for Rails 3.0 --- .../guides/source/action_mailer_basics.textile | 32 ++++++++++++++-------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile index a8e310f6ec..2405b8f28c 100644 --- a/railties/guides/source/action_mailer_basics.textile +++ b/railties/guides/source/action_mailer_basics.textile @@ -4,6 +4,8 @@ This guide should provide you with all you need to get started in sending and re endprologue. +WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in other versions of Rails. + h3. Introduction Action Mailer allows you to send emails from your application using a mailer model and views. So, in Rails, emails are used by creating mailers that inherit from +ActionMailer::Base+ and live in +app/mailers+. Those mailers have associated views that appear alongside controller views in +app/views+. @@ -55,8 +57,8 @@ end Here is a quick explanation of the items presented in the preceding method. For a full list of all available options, please have a look further down at the Complete List of ActionMailer user-settable attributes section. -|default Hash| This is a hash of default values for any email you send, in this case we are setting the :from header to a value for all messages in this class, this can be overridden on a per email basis| -|mail| The actual email message, we are passing the :to and :subject headers in| +* default Hash - This is a hash of default values for any email you send, in this case we are setting the :from header to a value for all messages in this class, this can be overridden on a per email basis +* +mail+ - The actual email message, we are passing the :to and :subject headers in| And instance variables we define in the method become available for use in the view. @@ -73,7 +75,10 @@ Create a file called +welcome_email.html.erb+ in +app/views/user_mailer/+. This

Welcome to example.com, <%= @user.name %>

- You have successfully signed up to example.com, and your username is: <%= @user.login %>.
+ You have successfully signed up to example.com, + your username is: <%= @user.login %>.
+

+

To login to the site, just follow this link: <%= @url %>.

Thanks for joining and have a great day!

@@ -87,7 +92,8 @@ It is also a good idea to make a text part for this email, to do this, create a Welcome to example.com, <%= @user.name %> =============================================== -You have successfully signed up to example.com, and your username is: <%= @user.login %>. +You have successfully signed up to example.com, +your username is: <%= @user.login %>. To login to the site, just follow this link: <%= @url %>. @@ -134,7 +140,7 @@ end Notice how we call UserMailer.welcome_email(user)? Even though in the user_mailer.rb file we defined an instance method, we are calling the method_name +welcome_email(user)+ on the class. This is a peculiarity of Action Mailer. -Note. In previous versions of Rails, you would call +deliver_welcome_email+ or +create_welcome_email+ however in Rails 3.0 this has been deprecated in favour of just calling the method name itself. +NOTE: In previous versions of Rails, you would call +deliver_welcome_email+ or +create_welcome_email+ however in Rails 3.0 this has been deprecated in favour of just calling the method name itself. The method +welcome_email+ returns a Mail::Message object which can then just be told +deliver+ to send itself out. @@ -179,7 +185,7 @@ Adding attachments has been simplified in Action Mailer 3.0. attachments['filename.jpg'] = File.read('/path/to/filename.jpg') -Note. Mail will automatically Base64 encode an attachment, if you want something different, pre encode your content and pass in the encoded content and encoding in a +Hash+ to the +attachments+ method. +NOTE: Mail will automatically Base64 encode an attachment, if you want something different, pre encode your content and pass in the encoded content and encoding in a +Hash+ to the +attachments+ method. * Pass the file name and specify headers and content and Action Mailer and Mail will use the settings you pass in. @@ -190,7 +196,7 @@ attachments['filename.jpg'] = {:mime_type => 'application/x-gzip', :content => encoded_content } -Note. If you specify an encoding, Mail will assume that your content is already encoded and not try to Base64 encode it. +NOTE: If you specify an encoding, Mail will assume that your content is already encoded and not try to Base64 encode it. h4. Mailer Views @@ -253,7 +259,9 @@ URLs can be generated in mailer views using +url_for+ or named routes. Unlike controllers, the mailer instance doesn't have any context about the incoming request so you'll need to provide the +:host+, +:controller+, and +:action+: -<%= url_for(:host => "example.com", :controller => "welcome", :action => "greeting") %> +<%= url_for(:host => "example.com", + :controller => "welcome", + :action => "greeting") %> When using named routes you only need to supply the +:host+: @@ -266,7 +274,7 @@ Email clients have no web context and so paths have no base URL to form complete It is also possible to set a default host that will be used in all mailers by setting the +:host+ option in the +ActionMailer::Base.default_url_options+ hash as follows: - + class UserMailer < ActionMailer::Base default_url_options[:host] = "example.com" @@ -277,7 +285,7 @@ class UserMailer < ActionMailer::Base :subject => "Welcome to My Awesome Site") end end - + h4. Sending Multipart Emails @@ -323,9 +331,9 @@ h3. Receiving Emails Receiving and parsing emails with Action Mailer can be a rather complex endeavour. Before your email reaches your Rails app, you would have had to configure your system to somehow forward emails to your app, which needs to be listening for that. So, to receive emails in your Rails app you'll need: -1. Implement a +receive+ method in your mailer. +* Implement a +receive+ method in your mailer. -2. Configure your email server to forward emails from the address(es) you would like your app to receive to +/path/to/app/script/runner 'UserMailer.receive(STDIN.read)'+. +* Configure your email server to forward emails from the address(es) you would like your app to receive to +/path/to/app/script/runner 'UserMailer.receive(STDIN.read)'+. Once a method called +receive+ is defined in any mailer, Action Mailer will parse the raw incoming email into an email object, decode it, instantiate a new mailer, and pass the email object to the mailer +receive+ instance method. Here's an example: -- cgit v1.2.3 From 428bdb50631ba03847367a15fedb2289269cd789 Mon Sep 17 00:00:00 2001 From: Mikel Lindsaar Date: Mon, 1 Feb 2010 21:16:44 +1100 Subject: Adding Release Notes 3.0 --- railties/guides/source/3_0_release_notes.textile | 487 +++++++++++++++++++++++ 1 file changed, 487 insertions(+) create mode 100644 railties/guides/source/3_0_release_notes.textile 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..385b621774 --- /dev/null +++ b/railties/guides/source/3_0_release_notes.textile @@ -0,0 +1,487 @@ +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, including a major overhaul of the router and query APIs. + +One of the major achievements of this release, is that while there are loads of new features, old APIs have been deprecated with warnings wherever possible, so that you can implement the new features and conventions at your own pace. There is a backwards compatibility layer that will be supported during 3.0.x and removed in 3.1. + +Rails 3.0 adds Active Model ORM abstraction, Abstract Controller generic controller 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 don'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. + +endprologue. + +h3. Upgrading from Rails 2.3.5 to Rails 3.0 + +As always, having a high coverage, passing test suite is your friend when upgrading. You should also first upgrade to Rails 2.3.5 and make sure your application still runs as expected before attempting to update to Rails 3.0. In general, the upgrade from Rails 2.x to 3.0 centers around three big changes: + +h4. New Ruby Version Requirement + +WARNING: Rails only runs on version 1.8.7 of Ruby or later. Support for previous versions of Ruby has been dropped and Rails 3.0 will no longer boot on any of these versions. + +h4. The new boot process + +As part of the shift to treating Rails apps as Rack endpoints, you are now required to have a +config/application.rb+ file, which takes over much of the work +config/environment.rb+ used to handle. Along with that comes a lot of internal change to the boot process (most of which you as a consumer of the framework don't care about!). + +h4. Gems and gems and gems + +The +config.gem+ method is gone and has been replaced by using +bundler+ and a +Gemfile+, see "Vendoring Gems":#vendoring-gems below. + +h4. New API's + +Both the router and query interface have seen significant, breaking changes. There is a backwards compatibility layer that is in place and will be supported until the 3.1 release. + +h4. Upgrade Process + +To help with the upgrade process, a plugin named "Rails Upgrade":http://github.com/rails/rails_upgrade has been created to automate part of the process. + +Simply install the plugin, then run +rake rails:upgrade:check+ to check your app for pieces that need to be updated (with links to information on how to update them). It also offers a task to generate a Gemfile based on your current +config.gem+ calls and a task to generate a new routes file from your current one. To get the plugin, simply run the following: + + +script/plugin install git://github.com/rails/rails_upgrade.git + + +You can see an example of how that works at "Rails Upgrade is now an Official Plugin":http://omgbloglol.com/post/364624593/rails-upgrade-is-now-an-official-plugin + +Aside from Rails Upgrade tool, if you need more help, there are people on IRC and "rubyonrails-talk":http://groups.google.com/group/rubyonrails-talk that are probably doing the same thing, possibly hitting the same issues. Be sure to blog your own experiences when upgrading so others can benefit from your knowledge! + +More information - "The Path to Rails 3: Approaching the upgrade":http://omgbloglol.com/post/353978923/the-path-to-rails-3-approaching-the-upgrade + +h3. Application Creation + +As stated above, you must be on Ruby 1.8.7 or later to boot up a Rails application. Rails will no longer boot on Ruby 1.8.6 or earlier. + +Rails 3.0 is designed to run on 1.8.7 and also support Ruby 1.9. + +There have been a few changes to the +rails+ script that's used to generate Rails applications: + +* The application name, rails my_app, can now optionally be a path instead rails ~/code/my_app, your rails application will be name spaced under the application name you pass the +rails+ command. +* Additionally, any flags you need to generate the application now need to come after the application path, for example: + + +$ rails myapp --database=mysql + + +h4. Vendoring Gems + +Rails now uses a +Gemfile+ in the application root to determine the gems you require for your application to start. This +Gemfile+ is then read and acted on by the new "Bundler":http://github.com/wycats/bundler gem, which then vendors all your gems into the vendor directory, making your Rails application isolated from system gems. + +More information: - "Using bundler":http://yehudakatz.com/2009/11/03/using-the-new-gem-bundler-today/ + +h4. Living on the Edge + +Due to the use of Gemfile, the concept of freezing Rails was dropped, because it's always bundled/frozen inside your application. By default, it uses your system gems when bundling; however, if you want to bundle straight from the Git repository, you can pass the edge flag: + + +$ rails myapp --edge + + +More information: +* "Spinning up a new Rails app":http://yehudakatz.com/2009/12/31/spinning-up-a-new-rails-app/ +* "Rails 3 and Passenger":http://cakebaker.42dh.com/2010/01/17/rails-3-and-passenger/ + +h3. Rails Architectural Changes + +There are six major architectural changes in the architecture of Rails. + +h4. Railties Restrung + +Railties was updated to provide a consistent plugin API for the entire Rails framework as well as a total rewrite of generators and the Rails bindings, the result is that developers can now hook into any significant stage of the generators and application framework in a consistent, defined manner. + +h4. All Rails core components are decoupled + +With the merge of Merb and Rails, one of the big jobs was to remove the tight coupling between Rails core components. This has now been achieved, and all Rails core components are now using the same API that you can use for developing plugins. This means any plugin you make, or any core component replacement (like DataMapper or Sequel) can access all the functionality that the Rails core components have access to and extend and enhance at will. + +More information: - "The Great Decoupling":http://yehudakatz.com/2009/07/19/rails-3-the-great-decoupling/ + +h4. Active Model Abstraction + +Part of decoupling the core components was extracting all ties to Active Record from Action Pack. This has now been completed. All new ORM plugins now just need to implement Active Model interfaces to work seamlessly with Action Pack. + +More information: - "Make Any Ruby Object Feel Like ActiveRecord":http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/ + +h4. Controller Abstraction + +Another big part of decoupling the core components was creating a base superclass that is separated from the notions of HTTP in order to handle rendering of views etc. This creation of +AbstractController+ allowed +ActionController+ and +ActionMailer+ to be greatly simplified with common code removed from all these libraries and put into Abstract Controller. + +More Information: - "Rails Edge Architecture":http://yehudakatz.com/2009/06/11/rails-edge-architecture/ + +h4. Arel Integration + +"Arel":http://github.com/brynary/arel (or Active Relation) has been taken on as the underpinnings of Active Record and is now required for Rails (it is installed for you when you do a gem bundle). Arel provides an SQL abstraction that simplifies out Active Record and provides the underpinnings for the relation functionality in Active Record. + +More information: - "Why I wrote Arel":http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/. + +h4. Mail Extraction + +Action Mailer ever since it's beginnings has had monkey patches, pre parsers and even delivery and receiver agents, all in addition to having TMail vendor'd in the source tree. Version 3 changes that with all email message related functionality abstracted out to the "Mail":http://github.com/mikel/mail gem. This again reduces code duplication and helps create definable boundaries between Action Mailer and the email parser. + +More information: - "New Action Mailer API in Rails 3":http://lindsaar.net/2010/1/26/new-actionmailer-api-in-rails-3 + +h3. Documentation + +The documentation in the Rails tree is being updated with all the API changes, additionally, the "Rails Edge guides":http://guides.rails.info/ are being updated one by one to reflect the changes in Rails 3.0. The guides at "guides.rubyonrails.org":http://guides.rubyonrails.org/ however will continue to contain only the stable version of Rails (at this point, version 2.3.5, until 3.0 is released). + +More Information: - "Rails Documentation Projects":http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects. + +h3. Railties + +With the decoupling of the main Rails frameworks, Railties got a huge overhaul so as to make linking up frameworks, engines or plugins as painless and extensible as possible: + +* Each application now has it's own name space, application is started with YourAppName.boot for example, makes interacting with other applications a lot easier. +* You now have access to Rails.config which provides huge amount of configuration settings for your application. +* Anything under Rails.root/app is now added to the load path, so you can make app/observers/user_observer.rb and Rails will load it without any modifications. +* Rails 3.0 now provides a Rails.config object, which provides a central repository of all sorts of Rails wide configuration options. + +Application generation has received extra flags allowing you to skip the installation of test-unit, Active Record, Prototype and Git. Also a new --dev flag has been added which sets the application up with the Gemfile pointing to your Rails checkout (which is determined by the path to the +rails+ binary). See rails --help for more info. + +Railties generators got a huge amount of attention in Rails 3.0, basically: + +* Generators were completely rewritten and are backwards incompatible. +* Rails templates API and generators API were merged (they are the same as the former). +* Generators are no longer loaded from special paths anymore, they are just found in the Ruby load path, so calling script/generate foo will look for generators/foo_generator. +* new generators provide hooks, so any template engine, ORM, test framework can easily hook in. +* new generators allow you to override the templates by placing a copy at RAILS_ROOT/lib/templates. +* Rails::Generators::TestCase is also supplied so you can create your own generators and test them. + +Also, the views generated by Railties generators had some overhaul: + +* Views now use +div+ tags instead of +p+ tags. +* Scaffolds generated now make use of _form partials, instead of duplicated code in the edit and new views. +* Scaffold forms now use f.submit which returns "Create ModelName" or "Update ModelName" depending on the state of the object passed in. + +Following deprecations were done in Railties: + +* RAILS_ROOT is deprecated in favour of Rails.root. +* RAILS_ENV is deprecated in favour of Rails.env. +* RAILS_DEFAULT_LOGGER is deprecated in favour of Rails.logger. +* PLUGIN/rails/tasks, PLUGIN/tasks are no longer loaded all tasks now must be in PLUGIN/lib/tasks. + +More information: +* "Discovering Rails 3 generators":http://blog.plataformatec.com.br/2010/01/discovering-rails-3-generators +* "Making Generators for Rails 3 with Thor":http://caffeinedd.com/guides/331-making-generators-for-rails-3-with-thor + +h3. Action Pack + +There have been significant internal and external changes in Action Pack. + +h4. Abstract Controller + +Abstract Controller pulls out the generic parts of Action Controller into a reusable module that any library can use to render templates, render partials, helpers, translations, logging, any part of the request response cycle. This abstraction allowed ActionMailer::Base to now just inherit from +AbstractController+ and just wrap the Rails DSL onto the Mail gem. + +It also provided an opportunity to clean up Action Controller, abstracting out what could to simplify the code. + +Note however that Abstract Controller is not a user facing API, you will not run into it in your day to day use of Rails. + +More Information: - "Rails Edge Architecture":http://yehudakatz.com/2009/06/11/rails-edge-architecture/ + +h4. Action Controller + +* application_controller.rb now has protect_from_forgery on by default. +* The cookie_verifier_secret has been moved to initializers/cookie_verification_secret.rb. +* The session_store configuration has moved to initializers/session_store.rb. +* cookies.secure allowing you to set encrypted values in cookies with cookie.secure[:key] => value. +* cookies.permanent allowing you to set permanent values in the cookie hash cookie.permanent[:key] => value that raise exceptions on signed values if verification failures. +* You can now pass :notice => 'This is a flash message' or :alert => 'Something went wrong' to the format call inside a +respond_to+ block. The flash[] hash still works as previously. +* respond_with method has now been added to your controllers simplifying the venerable +format+ blocks. +* ActionController::Responder added allowing you flexibility in how your responses get generated. + +Deprecations: + +* filter_parameter_logging is deprecated in favour of config.filter_parameters << :password. + +More Information: +* "Render Options in Rails 3":http://www.engineyard.com/blog/2010/render-options-in-rails-3/ +* "Three reasons to love ActionController::Responder":http://weblog.rubyonrails.org/2009/8/31/three-reasons-love-responder + +h4. Action Dispatch + +Action Dispatch is new in Rails 3.0 and provides a new, cleaner implementation for routing. + +* Big clean up and re-write of the router, the Rails router is now +rack_mount+ with a Rails DSL on top, it is a stand alone piece of software. +* Routes defined by each application are now name spaced within your Application module, that is: + + +# Instead of: + +ActionController::Routing::Routes.draw do + map.resources :posts +end + +# You do: + +AppName::Application.routes do + resources :posts +end + + +* Added +match+ method to the router, you can also pass any Rack application to the matched route. +* Added +constraints+ method to the router, allowing you to guard routers with defined constraints. +* Added +scope+ method to the router, allowing you to namespace routes for different languages or different actions, for example: + + +scope 'es' { resources :projects, + :path_names => { :edit => 'cambiar' }, + :as => 'projeto' } + +# Gives you the edit action with /es/projeto/1/cambiar + + +* Added +root+ method to the router as a short cut for match '/', :to => path. +* You can pass optional segments into the match, for example match "/:controller(/:action(/:id))(.:format)", each parenthesized segment is optional. +* Routes can be expressed via blocks, for example you can call controller :home { match '/:action' }. + +NOTE. The old style map commands still work as before with a backwards compatibility layer, however this will be removed in the 3.1 release. + +Deprecations + +* The catch all route for non-REST applications (/:controller/:action/:id) is now commented out. +* Routes :path_prefix no longer exists and :name_prefix now automatically adds "_" at the end of the given value. + +More Information: +* "The Rails 3 Router: Rack it Up":http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/ +* "Revamped Routes in Rails 3":http://rizwanreza.com/2009/12/20/revamped-routes-in-rails-3 +* "Generic Actions in Rails 3":http://yehudakatz.com/2009/12/20/generic-actions-in-rails-3/ + +h4. Action View + +Major re-write was done in the Action View helpers, implementing Unobtrusive JavaScript (UJS) hooks and removing the old inline AJAX commands. This enables Rails to use any compliant UJS driver to implement the UJS hooks in the helpers. + +What this means is that all previous remote_ helpers have been removed from Rails core and put into the "Prototype Legacy Helper":http://github.com/rails/prototype_legacy_helper. To get UJS hooks into your HTML, you now pass :remote => true instead. For example: + + +form_for @post, :remote => true + + +Produces: + + +
+ + +* You no longer need to call h(string) to escape HTML output, it is on by default in all view templates. If you want the unescaped string, call raw(string). +* Helpers now output HTML 5 by default. +* Form label helper now pulls values from I18n with a single value, so f.label :name will pull the :name translation. +* I18n select label on should now be :en.helpers.select instead of :en.support.select. +* You no longer need to place a minus sign at the end of a ruby interpolation inside an ERb template to remove the trailing carriage return in the HTML output. + +h3. Active Model + +Active Model is new in Rails 3.0. It provides an abstraction layer for any ORM libraries to use to interact with Rails by implementing an Active Model interface. + +h4. ORM Abstraction and Action Pack Interface + +Part of decoupling the core components was extracting all ties to Active Record from Action Pack. This has now been completed. All new ORM plugins now just need to implement Active Model interfaces to work seamlessly with Action Pack. + +More Information: - "Make Any Ruby Object Feel Like ActiveRecord":http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/ + +h4. Validations + +Validations have been moved, in the main, from Active Record into Active Model, providing an interface to validations that works across ORM libraries in Rails 3. + +* There is now a validates :attribute, options_hash short cut method that you can call that allows you to pass options for all the validates class methods, you can pass more than one option to a validate method. +* The +validates+ method has the following options: + * :acceptance => Boolean. + * :confirmation => Boolean. + * :exclusion => { :in => Ennumerable }. + * :inclusion => { :in => Ennumerable }. + * :format => { :with => Regexp, :on => :create }. + * :length => { :maximum => Fixnum }. + * :numericality => Boolean. + * :presence => Boolean. + * :uniqueness => Boolean. + +NOTE: All the Rails version 2.3 style validation methods are still supported in Rails 3.0, the new validates method is designed as an additional aid in your model validations, not a replacement for the existing API. + +You can also pass in a Validator object, which you can then reuse between objects that use Active Model: + + +class TitleValidator < ActiveModel::EachValidator + Titles = ['Mr.', 'Mrs.', 'Dr.'] + def validate_each(record, attribute, value) + unless Titles.include?(value) + record.errors[attribute] << 'must be a valid title' + end + end +end + + + +class Person + include ActiveModel::Validations + attr_accessor :title + validates :title, :presence => true, :title => true +end + +# Or for Active Record + +class Person < ActiveRecord::Base + validates :title, :presence => true, :title => true +end + + +More Information: +* "Sexy Validation in Rails 3":http://thelucid.com/2010/01/08/sexy-validation-in-edge-rails-rails-3/ +* "Rails 3 Validations Explained":http://lindsaar.net/2010/1/31/validates_rails_3_awesome_is_true + +h3. Active Record + +h4. Query Interface + +Active Record, through the use of Arel, now returns relations on it's core methods. The existing API in Rails 2.3.x is still supported and will not be deprecated until Rails 3.1 and not removed until Rails 3.2, however, the new API provides the following new methods that all return relations allowing them to be chained together: + +* where - provides conditions on the relation, what gets returned. +* select - choose what attributes of the models you wish to have returned from the database. +* group - groups the relation on the attribute supplied. +* having - provides an expression limiting group relations (GROUP BY constraint). +* joins - joins the relation to another table. +* clause - provides an expression limiting join relations (JOIN constraint). +* includes - includes other relations pre-loaded. +* order - orders the relation based on the expression supplied. +* limit - limits the relation to the number of records specified. +* lock - locks the records returned from the table. +* readonly - returns an read only copy of the data. +* from - provides a way to select relationships from more than one table. +* scope - (previously +named_scope+) return relations and can be chained together with the other relation methods. +* with_scope - and +with_exclusive_scope+ now also return relations and so can be chained. +* default_scope - also works with relations. + +More Information: +* "Active Record Query Interface":http://m.onkey.org/2010/1/22/active-record-query-interface +* "Let your SQL Growl in Rails 3":http://hasmanyquestions.wordpress.com/2010/01/17/let-your-sql-growl-in-rails-3/ + +h4. Patches and Deprecations + +Additionally, many fixes in the Active Record branch: + +* SQLite 2 support has been dropped in favour of SQLite 3. +* MySQL support for column order. +* PostgreSQL adapter has had it's +TIME ZONE+ support fixed so it no longer inserts incorrect values. +* PostgreSQL support for the XML data type column. +* +table_name+ is now cached. + +As well as the following deprecations: + +* +named_scope+ in an Active Record class is deprecated and has been renamed to just +scope+. +* In +scope+ methods, you should move to using the relation methods, instead of a :conditions => {} finder method, for example scope :since, lambda {|time| where("created_at > ?", time) }. +* save(false) is deprecated, in favour of save(:validate => false). +* I18n error messages for ActiveRecord should be changed from :en.activerecord.errors to :en.errors. +* model.errors.on is deprecated in favour of model.errors[] +* validates_presence_of => validates... :presence => true +* ActiveRecord::Base.colorize_logging and config.active_record.colorize_logging are deprecated in favour of Rails::Subscriber.colorize_logging or config.colorize_logging + +h3. Active Resource + +Active Resource was also extracted out to Active Model allowing you to use Active Resource objects with Action Pack seamlessly. + +* Added validations to Active Resource through Active Model. +* Add observing hooks to Active Resources. +* HTTP proxy support for Active Resource. +* Added digest authentication option for ActiveResource. +* Move model naming into Active Model. +* Changed Active Resource attributes to a Hash with indifferent access. +* Added +first+, +last+ and +all+ aliases for equivalent find scopes. +* find_every now does not return a +ResourceNotFound+ error if nothing returned. +* Added save! which raises ResourceInvalid unless the object is valid?. +* update_attribute and update_attributes added to Active Resource models. +* Added exists?. +* Rename SchemaDefinition to Schema and define_schema to schema. +* Use the format of Active Resources rather than the content-type of remote errors to load errors. +* Use instance_eval for schema block. +* Fix ActiveResource::ConnectionError#to_s when @response does not respond to #code or #message, handles Ruby 1.9 compat. +* Add support for errors in JSON format. +* Ensure load works with numeric arrays. +* Recognises a 410 response from remote resource as the resource has been deleted. +* Add ability to set SSL options on Active Resource connections. +* Setting connection timeout also affects Net::HTTP open_timeout. + +Deprecations: + +* save(false) is deprecated, in favour of save(:validate => false). +* Ruby 1.9.2: URI.parse and .decode are deprecated and are no longer used in the library. + +h3. Active Support + +A large effort was made in Active Support to make it cherry pickable, that is, you no longer have to require the entire Active Support library to get pieces of it. This allows the various core components of Rails to run slimmer. + +Following changes in Active Support: + +* Large clean up of the library removing unused methods throughout. +* Active Support no longer provides vendor'd versions of "TZInfo":http://tzinfo.rubyforge.org/, "Memcache Client":http://deveiate.org/projects/RMemCache/ and "Builder":http://builder.rubyforge.org/, these are all included as dependencies and installed via the gem bundle command. +* Safe buffers are implemented in ActiveSupport::SafeBuffer. +* Added Array.uniq_by and Array.uniq_by!. +* Fixed bug on TimeZone.seconds_to_utc_offset returning wrong value. +* Added ActiveSupport::Notifications middleware. +* ActiveSupport.use_standard_json_time_format now defaults to true. +* ActiveSupport.escape_html_entities_in_json now defaults to false. +* Integer#multiple_of? accepts zero as an argument, returns false unless the receiver is zero. +* +string.chars+ has been renamed to +string.mb_chars+. +* OrderedHash now can de-serialize through YAML. +* Added SAX-based parser for XmlMini, using LibXML and Nokogiri. +* Added Object#presence that returns the object if it's #present? otherwise returns +nil+. +* Added String#exclude? core extension that returns the inverse of #include?. +* Added #to_i to +DateTime+ in +ActiveSupport+ so #to_yaml works correctly on +ActiveRecord+ models with +DateTime+ attributes. +* Added Enumerable#exclude? to bring parity to Enumerable#include? and avoid if !x.include?. +* Switch to on-by-default XSS escaping for rails. +* Support deep-merging HashWithIndifferentAccess. +* Enumerable#sum now works will all enumerables, even if they don't respond to :size. +* #inspect of a zero length duration returns '0 seconds' instead of empty string. +* Add #element and #collection to ModelName. +* String #to_time and #to_datetime: handle fractional seconds. +* Added support to new callbacks for around filter object that respond to :before & :after used in before and after callbacks. +* ActiveSupport::OrderedHash#to_a method returns an ordered set of arrays. Matches Ruby 1.9's Hash#to_a. +* MissingSourceFile exists as a constant but it is now just equals to LoadError +* Added Class#class_attribute, to be able to declare a class-level attribute whose value is inheritable and overwritable by subclasses. +* Finally removed +DeprecatedCallbacks+ in ActiveRecord::Associations. + +The following methods have been removed because they are now available in Ruby 1.8.7 and 1.9. + +* Integer#even? and Integer#odd? +* String#each_char +* String#start_with? and String#end_with? (plural aliases still kept) +* String#bytesize +* Object#tap +* Symbol#to_proc +* Object#instance_variable_defined? +* Enumerable#none? + +The security patch for REXML remains in Active Support because early patchlevels still need it. It is only applied if needed though. + +The following methods have been removed because they are no longer used in the framework: + +* Class#subclasses, Class#reachable?, Class#remove_class +* Object#remove_subclasses_of, Object#subclasses_of, Object#extend_with_included_modules_from, Object#extended_by +* Regexp#number_of_captures +* Regexp.unoptionalize, Regexp.optionalize, Regexp#number_of_captures + + +h3. Action Mailer + +Action Mailer has been given a new API with TMail being replaced out with the new "Mail":http://github.com/mikel/mail as the Email library. Action Mailer itself has been given an almost complete re-write with pretty much every line of code touched. The result is that Action Mailer now simply inherits from Abstract Controller and wraps the Mail gem in a Rails DSL. This reduces the amount of code and duplication of other libraries in Action Mailer considerably. + +* All mailers are now in app/mailers by default. +* Can now send email using new API with three methods: +attachments+, +headers+ and +mail+. +* ActionMailer emailing methods now return Mail::Message objects, which can then be sent the +deliver+ message to send itself. +* All delivery methods are now abstracted out to the Mail gem. +* The mail delivery method can accept a hash of all valid mail header fields with their value pair. +* The mail delivery method acts in a similar way to Action Controller's respond_to block, and you can explicitly or implicitly render templates. Action Mailer will turn the email into a multipart email as needed. +* You can pass a proc to the format.mime_type calls within the mail block and explicitly render specific types of text, or add layouts or different templates. The +render+ call inside the proc is from Abstract Controller, so all the same options are available as they are in Action Controller. +* What were mailer unit tests have been moved to functional tests. + +Deprecations: + +* :charset, :content_type, :mime_version, :implicit_parts_order are all deprecated in favour of ActionMailer.default :key => value style declarations. +* Mailer dynamic create_method_name and deliver_method_name are deprecated, just call method_name which now returns a Mail::Message object. +* ActionMailer.deliver(message) is deprecated, just call message.deliver. +* template_root is deprecated, pass options to a render call inside a proc from the format.mime_type method inside the mail generation block +* The body method to define instance variables is deprecated (body {:ivar => value}), just declare instance variables in the method directly and they will be available in the view. +* Mailers being in app/models is deprecated, use app/mailers instead. + +More Information: +* "New Action Mailer API in Rails 3":http://lindsaar.net/2010/1/26/new-actionmailer-api-in-rails-3 +* "New Mail Gem for Ruby":http://lindsaar.net/2010/1/23/mail-gem-version-2-released + +h3. Credits + +See the "full list of contributors to Rails":http://contributors.rubyonrails.org/, many people have spent many hours making Rails 3 what it is. Kudos to all of them. + +Rails 3.0 Release Notes were compiled by "Mikel Lindsaar":http://lindsaar.net, who can be found "feeding":http://feeds.feedburner.com/lindsaar-net and "tweeting":http://twitter.com/raasdnil. \ No newline at end of file -- cgit v1.2.3 From ddf2b4add33d5e54c5f5e7adacadbb50d3fa7b52 Mon Sep 17 00:00:00 2001 From: Mikel Lindsaar Date: Tue, 2 Feb 2010 09:46:55 +1100 Subject: Xavier's edits --- railties/guides/source/3_0_release_notes.textile | 85 ++++++++++++------------ 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/railties/guides/source/3_0_release_notes.textile b/railties/guides/source/3_0_release_notes.textile index 385b621774..afc88af643 100644 --- a/railties/guides/source/3_0_release_notes.textile +++ b/railties/guides/source/3_0_release_notes.textile @@ -1,8 +1,8 @@ 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, including a major overhaul of the router and query APIs. +Rails 3.0 is a landmark release as it delivers on the Merb/Rails merge promise made in December 2008. Rails 3.0 provides major upgrades to all of the components of Rails, including a complete overhaul of the router and query APIs. -One of the major achievements of this release, is that while there are loads of new features, old APIs have been deprecated with warnings wherever possible, so that you can implement the new features and conventions at your own pace. There is a backwards compatibility layer that will be supported during 3.0.x and removed in 3.1. +One of the main achievements of this release, is that while there are loads of new features, old APIs have been deprecated with warnings wherever possible, so that you can implement the new features and conventions at your own pace. There is a backwards compatibility layer that will be supported during 3.0.x and removed in 3.1. Rails 3.0 adds Active Model ORM abstraction, Abstract Controller generic controller 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. @@ -20,21 +20,21 @@ WARNING: Rails only runs on version 1.8.7 of Ruby or later. Support for previou h4. The new boot process -As part of the shift to treating Rails apps as Rack endpoints, you are now required to have a +config/application.rb+ file, which takes over much of the work +config/environment.rb+ used to handle. Along with that comes a lot of internal change to the boot process (most of which you as a consumer of the framework don't care about!). +As part of the shift to treating Rails apps as Rack endpoints, you are now required to have a +config/application.rb+ file, which takes over much of the work +config/environment.rb+ used to handle. Along with that comes a lot of internal change to the boot process, but those changes are mostly internal. h4. Gems and gems and gems The +config.gem+ method is gone and has been replaced by using +bundler+ and a +Gemfile+, see "Vendoring Gems":#vendoring-gems below. -h4. New API's +h4. New APIs Both the router and query interface have seen significant, breaking changes. There is a backwards compatibility layer that is in place and will be supported until the 3.1 release. h4. Upgrade Process -To help with the upgrade process, a plugin named "Rails Upgrade":http://github.com/rails/rails_upgrade has been created to automate part of the process. +To help with the upgrade process, a plugin named "Rails Upgrade":http://github.com/rails/rails_upgrade has been created to automate part of it. -Simply install the plugin, then run +rake rails:upgrade:check+ to check your app for pieces that need to be updated (with links to information on how to update them). It also offers a task to generate a Gemfile based on your current +config.gem+ calls and a task to generate a new routes file from your current one. To get the plugin, simply run the following: +Simply install the plugin, then run +rake rails:upgrade:check+ to check your app for pieces that need to be updated (with links to information on how to update them). It also offers a task to generate a +Gemfile+ based on your current +config.gem+ calls and a task to generate a new routes file from your current one. To get the plugin, simply run the following: script/plugin install git://github.com/rails/rails_upgrade.git @@ -69,7 +69,7 @@ More information: - "Using bundler":http://yehudakatz.com/2009/11/03/using-the-n h4. Living on the Edge -Due to the use of Gemfile, the concept of freezing Rails was dropped, because it's always bundled/frozen inside your application. By default, it uses your system gems when bundling; however, if you want to bundle straight from the Git repository, you can pass the edge flag: +Due to the use of +Gemfile+, the concept of freezing Rails was dropped, because it's always bundled/frozen inside your application. By default, it uses your system gems when bundling; however, if you want to bundle straight from the Git repository, you can pass the edge flag: $ rails myapp --edge @@ -81,7 +81,7 @@ More information: h3. Rails Architectural Changes -There are six major architectural changes in the architecture of Rails. +There are six major changes in the architecture of Rails. h4. Railties Restrung @@ -113,7 +113,7 @@ More information: - "Why I wrote Arel":http://magicscalingsprinkles.wordpress.co h4. Mail Extraction -Action Mailer ever since it's beginnings has had monkey patches, pre parsers and even delivery and receiver agents, all in addition to having TMail vendor'd in the source tree. Version 3 changes that with all email message related functionality abstracted out to the "Mail":http://github.com/mikel/mail gem. This again reduces code duplication and helps create definable boundaries between Action Mailer and the email parser. +Action Mailer ever since its beginnings has had monkey patches, pre parsers and even delivery and receiver agents, all in addition to having TMail vendored in the source tree. Version 3 changes that with all email message related functionality abstracted out to the "Mail":http://github.com/mikel/mail gem. This again reduces code duplication and helps create definable boundaries between Action Mailer and the email parser. More information: - "New Action Mailer API in Rails 3":http://lindsaar.net/2010/1/26/new-actionmailer-api-in-rails-3 @@ -128,19 +128,19 @@ h3. Railties With the decoupling of the main Rails frameworks, Railties got a huge overhaul so as to make linking up frameworks, engines or plugins as painless and extensible as possible: * Each application now has it's own name space, application is started with YourAppName.boot for example, makes interacting with other applications a lot easier. -* You now have access to Rails.config which provides huge amount of configuration settings for your application. +* You now have access to Rails.config which provides huge amount of configuration settings for your application. * Anything under Rails.root/app is now added to the load path, so you can make app/observers/user_observer.rb and Rails will load it without any modifications. * Rails 3.0 now provides a Rails.config object, which provides a central repository of all sorts of Rails wide configuration options. -Application generation has received extra flags allowing you to skip the installation of test-unit, Active Record, Prototype and Git. Also a new --dev flag has been added which sets the application up with the Gemfile pointing to your Rails checkout (which is determined by the path to the +rails+ binary). See rails --help for more info. +Application generation has received extra flags allowing you to skip the installation of test-unit, Active Record, Prototype and Git. Also a new --dev flag has been added which sets the application up with the +Gemfile+ pointing to your Rails checkout (which is determined by the path to the +rails+ binary). See rails --help for more info. Railties generators got a huge amount of attention in Rails 3.0, basically: * Generators were completely rewritten and are backwards incompatible. * Rails templates API and generators API were merged (they are the same as the former). * Generators are no longer loaded from special paths anymore, they are just found in the Ruby load path, so calling script/generate foo will look for generators/foo_generator. -* new generators provide hooks, so any template engine, ORM, test framework can easily hook in. -* new generators allow you to override the templates by placing a copy at RAILS_ROOT/lib/templates. +* New generators provide hooks, so any template engine, ORM, test framework can easily hook in. +* New generators allow you to override the templates by placing a copy at RAILS_ROOT/lib/templates. * Rails::Generators::TestCase is also supplied so you can create your own generators and test them. Also, the views generated by Railties generators had some overhaul: @@ -149,12 +149,13 @@ Also, the views generated by Railties generators had some overhaul: * Scaffolds generated now make use of _form partials, instead of duplicated code in the edit and new views. * Scaffold forms now use f.submit which returns "Create ModelName" or "Update ModelName" depending on the state of the object passed in. -Following deprecations were done in Railties: +Railties now deprecates: -* RAILS_ROOT is deprecated in favour of Rails.root. -* RAILS_ENV is deprecated in favour of Rails.env. -* RAILS_DEFAULT_LOGGER is deprecated in favour of Rails.logger. -* PLUGIN/rails/tasks, PLUGIN/tasks are no longer loaded all tasks now must be in PLUGIN/lib/tasks. +* RAILS_ROOT in favour of Rails.root, +* RAILS_ENV in favour of Rails.env, and +* RAILS_DEFAULT_LOGGER in favour of Rails.logger. + +PLUGIN/rails/tasks, and PLUGIN/tasks are no longer loaded all tasks now must be in PLUGIN/lib/tasks. More information: * "Discovering Rails 3 generators":http://blog.plataformatec.com.br/2010/01/discovering-rails-3-generators @@ -255,7 +256,7 @@ form_for @post, :remote => true Produces: - + * You no longer need to call h(string) to escape HTML output, it is on by default in all view templates. If you want the unescaped string, call raw(string). @@ -276,9 +277,9 @@ More Information: - "Make Any Ruby Object Feel Like ActiveRecord":http://yehudak h4. Validations -Validations have been moved, in the main, from Active Record into Active Model, providing an interface to validations that works across ORM libraries in Rails 3. +Validations have been moved from Active Record into Active Model, providing an interface to validations that works across ORM libraries in Rails 3. -* There is now a validates :attribute, options_hash short cut method that you can call that allows you to pass options for all the validates class methods, you can pass more than one option to a validate method. +* There is now a validates :attribute, options_hash shortcut method that allows you to pass options for all the validates class methods, you can pass more than one option to a validate method. * The +validates+ method has the following options: * :acceptance => Boolean. * :confirmation => Boolean. @@ -292,7 +293,7 @@ Validations have been moved, in the main, from Active Record into Active Model, NOTE: All the Rails version 2.3 style validation methods are still supported in Rails 3.0, the new validates method is designed as an additional aid in your model validations, not a replacement for the existing API. -You can also pass in a Validator object, which you can then reuse between objects that use Active Model: +You can also pass in a validator object, which you can then reuse between objects that use Active Model: class TitleValidator < ActiveModel::EachValidator @@ -373,26 +374,26 @@ h3. Active Resource Active Resource was also extracted out to Active Model allowing you to use Active Resource objects with Action Pack seamlessly. -* Added validations to Active Resource through Active Model. -* Add observing hooks to Active Resources. -* HTTP proxy support for Active Resource. -* Added digest authentication option for ActiveResource. -* Move model naming into Active Model. +* Added validations through Active Model. +* Added observing hooks. +* HTTP proxy support. +* Added support for digest authentication. +* Moved model naming into Active Model. * Changed Active Resource attributes to a Hash with indifferent access. * Added +first+, +last+ and +all+ aliases for equivalent find scopes. * find_every now does not return a +ResourceNotFound+ error if nothing returned. * Added save! which raises ResourceInvalid unless the object is valid?. * update_attribute and update_attributes added to Active Resource models. * Added exists?. -* Rename SchemaDefinition to Schema and define_schema to schema. +* Renamed SchemaDefinition to Schema and define_schema to schema. * Use the format of Active Resources rather than the content-type of remote errors to load errors. * Use instance_eval for schema block. -* Fix ActiveResource::ConnectionError#to_s when @response does not respond to #code or #message, handles Ruby 1.9 compat. +* Fix ActiveResource::ConnectionError#to_s when +@response+ does not respond to #code or #message, handles Ruby 1.9 compat. * Add support for errors in JSON format. * Ensure load works with numeric arrays. * Recognises a 410 response from remote resource as the resource has been deleted. * Add ability to set SSL options on Active Resource connections. -* Setting connection timeout also affects Net::HTTP open_timeout. +* Setting connection timeout also affects +Net::HTTP+ open_timeout. Deprecations: @@ -403,32 +404,32 @@ h3. Active Support A large effort was made in Active Support to make it cherry pickable, that is, you no longer have to require the entire Active Support library to get pieces of it. This allows the various core components of Rails to run slimmer. -Following changes in Active Support: +These are the main changes in Active Support: * Large clean up of the library removing unused methods throughout. -* Active Support no longer provides vendor'd versions of "TZInfo":http://tzinfo.rubyforge.org/, "Memcache Client":http://deveiate.org/projects/RMemCache/ and "Builder":http://builder.rubyforge.org/, these are all included as dependencies and installed via the gem bundle command. +* Active Support no longer provides vendored versions of "TZInfo":http://tzinfo.rubyforge.org/, "Memcache Client":http://deveiate.org/projects/RMemCache/ and "Builder":http://builder.rubyforge.org/, these are all included as dependencies and installed via the gem bundle command. * Safe buffers are implemented in ActiveSupport::SafeBuffer. * Added Array.uniq_by and Array.uniq_by!. -* Fixed bug on TimeZone.seconds_to_utc_offset returning wrong value. +* Fixed bug on +TimeZone.seconds_to_utc_offset+ returning wrong value. * Added ActiveSupport::Notifications middleware. * ActiveSupport.use_standard_json_time_format now defaults to true. * ActiveSupport.escape_html_entities_in_json now defaults to false. * Integer#multiple_of? accepts zero as an argument, returns false unless the receiver is zero. * +string.chars+ has been renamed to +string.mb_chars+. -* OrderedHash now can de-serialize through YAML. +* +ActiveSupport::OrderedHash+ now can de-serialize through YAML. * Added SAX-based parser for XmlMini, using LibXML and Nokogiri. * Added Object#presence that returns the object if it's #present? otherwise returns +nil+. * Added String#exclude? core extension that returns the inverse of #include?. -* Added #to_i to +DateTime+ in +ActiveSupport+ so #to_yaml works correctly on +ActiveRecord+ models with +DateTime+ attributes. +* Added to_i to +DateTime+ in +ActiveSupport+ so to_yaml works correctly on models with +DateTime+ attributes. * Added Enumerable#exclude? to bring parity to Enumerable#include? and avoid if !x.include?. * Switch to on-by-default XSS escaping for rails. -* Support deep-merging HashWithIndifferentAccess. +* Support deep-merging in +ActiveSupport::HashWithIndifferentAccess+. * Enumerable#sum now works will all enumerables, even if they don't respond to :size. -* #inspect of a zero length duration returns '0 seconds' instead of empty string. -* Add #element and #collection to ModelName. -* String #to_time and #to_datetime: handle fractional seconds. -* Added support to new callbacks for around filter object that respond to :before & :after used in before and after callbacks. -* ActiveSupport::OrderedHash#to_a method returns an ordered set of arrays. Matches Ruby 1.9's Hash#to_a. +* inspect on a zero length duration returns '0 seconds' instead of empty string. +* Add element and collection to ModelName. +* String#to_time and String#to_datetime handle fractional seconds. +* Added support to new callbacks for around filter object that respond to :before and :after used in before and after callbacks. +* The ActiveSupport::OrderedHash#to_a method returns an ordered set of arrays. Matches Ruby 1.9's Hash#to_a. * MissingSourceFile exists as a constant but it is now just equals to LoadError * Added Class#class_attribute, to be able to declare a class-level attribute whose value is inheritable and overwritable by subclasses. * Finally removed +DeprecatedCallbacks+ in ActiveRecord::Associations. @@ -444,7 +445,7 @@ The following methods have been removed because they are now available in Ruby 1 * Object#instance_variable_defined? * Enumerable#none? -The security patch for REXML remains in Active Support because early patchlevels still need it. It is only applied if needed though. +The security patch for REXML remains in Active Support because early patchlevels of Ruby 1.8.7 still need it. Active Support knows whether it has to apply it or not. The following methods have been removed because they are no longer used in the framework: -- cgit v1.2.3 From b9edb0c60c11025311fb06f2e60b3354f1b6cb09 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Mon, 1 Feb 2010 15:28:31 -0800 Subject: Update the Gemfile and CI build --- Gemfile | 1 - ci/ci_build.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 68e6487e2c..051607c0f1 100644 --- a/Gemfile +++ b/Gemfile @@ -11,7 +11,6 @@ if RUBY_VERSION < '1.9' end # AR -gem "arel", ">= 0.2.0" gem "sqlite3-ruby", ">= 1.2.5" group :test do diff --git a/ci/ci_build.rb b/ci/ci_build.rb index 5b6d066c2f..c8f0f9946b 100755 --- a/ci/ci_build.rb +++ b/ci/ci_build.rb @@ -106,7 +106,7 @@ puts "[CruiseControl] #{`pg_config --version`}" puts "[CruiseControl] SQLite3: #{`sqlite3 -version`}" `gem env`.each_line {|line| print "[CruiseControl] #{line}"} puts "[CruiseControl] Bundled gems:" -`gem bundle --list`.each_line {|line| print "[CruiseControl] #{line}"} +# `gem bundle --list`.each_line {|line| print "[CruiseControl] #{line}"} puts "[CruiseControl] Local gems:" `gem list`.each_line {|line| print "[CruiseControl] #{line}"} -- cgit v1.2.3 From 485f12fe4cbf4ae9d22454a6a02c786ef10514f7 Mon Sep 17 00:00:00 2001 From: Mikel Lindsaar Date: Tue, 2 Feb 2010 11:24:19 +1100 Subject: Fixed --dev flag for new bundler --- railties/lib/generators/rails/app/app_generator.rb | 2 +- railties/lib/generators/rails/app/templates/Gemfile | 2 +- railties/test/generators/app_generator_test.rb | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/railties/lib/generators/rails/app/app_generator.rb b/railties/lib/generators/rails/app/app_generator.rb index 918bb98db8..dcb8af208f 100644 --- a/railties/lib/generators/rails/app/app_generator.rb +++ b/railties/lib/generators/rails/app/app_generator.rb @@ -181,7 +181,7 @@ module Rails::Generators end def bundle_if_dev_or_edge - run "gem bundle" if dev_or_edge? + run "bundle install" if dev_or_edge? end protected diff --git a/railties/lib/generators/rails/app/templates/Gemfile b/railties/lib/generators/rails/app/templates/Gemfile index 50f1a6a414..ce495177eb 100644 --- a/railties/lib/generators/rails/app/templates/Gemfile +++ b/railties/lib/generators/rails/app/templates/Gemfile @@ -7,7 +7,7 @@ gem "rails", "<%= Rails::VERSION::STRING %>" ## Bundle edge rails: <%- if options.dev? -%> -directory "<%= Rails::Generators::RAILS_DEV_PATH %>", :glob => "{*/,}*.gemspec" +path "<%= Rails::Generators::RAILS_DEV_PATH %>", :glob => "{*/,}*.gemspec" gem "rails", "<%= Rails::VERSION::STRING %>" <%- else -%> <%= "# " unless options.edge? %>gem "rails", :git => "git://github.com/rails/rails.git" diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index f821f5caf3..95a33c50f8 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -168,15 +168,15 @@ class AppGeneratorTest < Rails::Generators::TestCase end def test_dev_option - generator([destination_root], :dev => true).expects(:run).with("gem bundle") + generator([destination_root], :dev => true).expects(:run).with("bundle install") silence(:stdout){ generator.invoke } rails_path = File.expand_path('../../..', Rails.root) - dev_gem = %(directory #{rails_path.inspect}, :glob => "{*/,}*.gemspec") + dev_gem = %(path #{rails_path.inspect}, :glob => "{*/,}*.gemspec") assert_file 'Gemfile', /^#{Regexp.escape(dev_gem)}$/ end def test_edge_option - generator([destination_root], :edge => true).expects(:run).with("gem bundle") + generator([destination_root], :edge => true).expects(:run).with("bundle install") silence(:stdout){ generator.invoke } edge_gem = %(gem "rails", :git => "git://github.com/rails/rails.git") assert_file 'Gemfile', /^#{Regexp.escape(edge_gem)}$/ -- cgit v1.2.3 From 5998dd7bb84c76f4c8a272a71b588e2331989b4b Mon Sep 17 00:00:00 2001 From: Sam Ruby Date: Mon, 1 Feb 2010 19:47:31 -0500 Subject: Resolve view paths correctly on CygWin Signed-off-by: Mikel Lindsaar --- actionpack/lib/action_view/template/resolver.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 6878067f7c..8acfe6cad0 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -104,7 +104,7 @@ module ActionView end def query(path, exts) - query = "#{@path}/#{path}" + query = File.join(@path, path) exts.each do |ext| query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << '}' end -- cgit v1.2.3 From df8852d04d030330efcb86f16977b837473bf022 Mon Sep 17 00:00:00 2001 From: Sam Ruby Date: Fri, 29 Jan 2010 15:23:59 -0500 Subject: Make rdoc match current API Signed-off-by: Mikel Lindsaar --- actionpack/lib/action_dispatch/routing.rb | 152 +++++++++--------------------- 1 file changed, 47 insertions(+), 105 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index b598d6f7e2..335c9edb98 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -9,17 +9,18 @@ module ActionDispatch # mod_rewrite rules. Best of all, Rails' Routing works with any web server. # Routes are defined in config/routes.rb. # - # Consider the following route, installed by Rails when you generate your - # application: + # Consider the following route, which you will find commented out at the + # bottom of your generated config/routes.rb: # - # map.connect ':controller/:action/:id' + # match ':controller(/:action(/:id(.:format)))' # # This route states that it expects requests to consist of a - # :controller followed by an :action that in turn is fed - # some :id. + # :controller followed optionally by an :action that in + # turn is followed optionally by an :id, which in turn is followed + # optionally by a :format # - # Suppose you get an incoming request for /blog/edit/22, you'll end up - # with: + # Suppose you get an incoming request for /blog/edit/22, you'll end + # up with: # # params = { :controller => 'blog', # :action => 'edit', @@ -29,7 +30,7 @@ module ActionDispatch # Think of creating routes as drawing a map for your requests. The map tells # them where to go based on some predefined pattern: # - # ActionController::Routing::Routes.draw do |map| + # AppName::Applications.routes.draw do |map| # Pattern 1 tells some request to go to one place # Pattern 2 tell them to go to another # ... @@ -42,60 +43,16 @@ module ActionDispatch # # Other names simply map to a parameter as in the case of :id. # - # == Route priority - # - # Not all routes are created equally. Routes have priority defined by the - # order of appearance of the routes in the config/routes.rb file. The priority goes - # from top to bottom. The last route in that file is at the lowest priority - # and will be applied last. If no route matches, 404 is returned. - # - # Within blocks, the empty pattern is at the highest priority. - # In practice this works out nicely: - # - # ActionController::Routing::Routes.draw do |map| - # map.with_options :controller => 'blog' do |blog| - # blog.show '', :action => 'list' - # end - # map.connect ':controller/:action/:view' - # end - # - # In this case, invoking blog controller (with an URL like '/blog/') - # without parameters will activate the 'list' action by default. - # - # == Defaults routes and default parameters - # - # Setting a default route is straightforward in Rails - you simply append a - # Hash at the end of your mapping to set any default parameters. - # - # Example: - # - # ActionController::Routing:Routes.draw do |map| - # map.connect ':controller/:action/:id', :controller => 'blog' - # end - # - # This sets up +blog+ as the default controller if no other is specified. - # This means visiting '/' would invoke the blog controller. - # - # More formally, you can include arbitrary parameters in the route, thus: - # - # map.connect ':controller/:action/:id', :action => 'show', :page => 'Dashboard' - # - # This will pass the :page parameter to all incoming requests that match this route. - # - # Note: The default routes, as provided by the Rails generator, make all actions in every - # controller accessible via GET requests. You should consider removing them or commenting - # them out if you're using named routes and resources. - # # == Named routes # - # Routes can be named with the syntax map.name_of_route options, + # Routes can be named by passing an :as option, # allowing for easy reference within your source as +name_of_route_url+ # for the full URL and +name_of_route_path+ for the URI path. # # Example: # # # In routes.rb - # map.login 'login', :controller => 'accounts', :action => 'login' + # match '/login' => 'accounts#login', :as => 'login' # # # With render, redirect_to, tests, etc. # redirect_to login_url @@ -104,10 +61,10 @@ module ActionDispatch # # redirect_to show_item_path(:id => 25) # - # Use map.root as a shorthand to name a route for the root path "". + # Use root as a shorthand to name a route for the root path "". # # # In routes.rb - # map.root :controller => 'blogs' + # root :to => 'blogs#index' # # # would recognize http://www.example.com/ as # params = { :controller => 'blogs', :action => 'index' } @@ -116,20 +73,14 @@ module ActionDispatch # root_url # => 'http://www.example.com/' # root_path # => '' # - # You can also specify an already-defined named route in your map.root call: - # - # # In routes.rb - # map.new_session :controller => 'sessions', :action => 'new' - # map.root :new_session - # - # Note: when using +with_options+, the route is simply named after the + # Note: when using +controller+, the route is simply named after the # method you call on the block parameter rather than map. # # # In routes.rb - # map.with_options :controller => 'blog' do |blog| - # blog.show '', :action => 'list' - # blog.delete 'delete/:id', :action => 'delete' - # blog.edit 'edit/:id', :action => 'edit' + # controller :blog do + # match 'blog/show' => :list + # match 'blog/delete' => :delete + # match 'blog/edit/:id' => :edit # end # # # provides named routes for show, delete, and edit @@ -139,12 +90,13 @@ module ActionDispatch # # Routes can generate pretty URLs. For example: # - # map.connect 'articles/:year/:month/:day', - # :controller => 'articles', - # :action => 'find_by_date', - # :year => /\d{4}/, - # :month => /\d{1,2}/, - # :day => /\d{1,2}/ + # match '/articles/:year/:month/:day', :constraints => { + # :controller => 'articles', + # :action => 'find_by_date', + # :year => /\d{4}/, + # :month => /\d{1,2}/, + # :day => /\d{1,2}/ + # } # # Using the route above, the URL "http://localhost:3000/articles/2005/11/06" # maps to @@ -154,42 +106,34 @@ module ActionDispatch # == Regular Expressions and parameters # You can specify a regular expression to define a format for a parameter. # - # map.geocode 'geocode/:postalcode', :controller => 'geocode', - # :action => 'show', :postalcode => /\d{5}(-\d{4})?/ - # - # or, more formally: + # controller 'geocode' do + # match 'geocode/:postalcode' => :show', :constraints => { + # :postalcode => /\d{5}(-\d{4})?/ + # } # - # map.geocode 'geocode/:postalcode', :controller => 'geocode', - # :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ } - # - # Formats can include the 'ignorecase' and 'extended syntax' regular + # Constraints can include the 'ignorecase' and 'extended syntax' regular # expression modifiers: # - # map.geocode 'geocode/:postalcode', :controller => 'geocode', - # :action => 'show', :postalcode => /hx\d\d\s\d[a-z]{2}/i + # controller 'geocode' do + # match 'geocode/:postalcode' => :show', :constraints => { + # :postalcode => /hx\d\d\s\d[a-z]{2}/i + # } + # end # - # map.geocode 'geocode/:postalcode', :controller => 'geocode', - # :action => 'show',:requirements => { - # :postalcode => /# Postcode format - # \d{5} #Prefix - # (-\d{4})? #Suffix - # /x - # } + # controller 'geocode' do + # match 'geocode/:postalcode' => :show', :constraints => { + # :postalcode => /# Postcode format + # \d{5} #Prefix + # (-\d{4})? #Suffix + # /x + # } + # end # # Using the multiline match modifier will raise an ArgumentError. # Encoding regular expression modifiers are silently ignored. The # match will always use the default encoding or ASCII. # - # == Route globbing - # - # Specifying *[string] as part of a rule like: - # - # map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?' - # - # will glob all remaining parts of the route that were not recognized earlier. - # The globbed values are in params[:path] as an array of path segments. - # - # == Route conditions + # == HTTP Methods # # With conditions you can define restrictions on routes. Currently the only valid condition is :method. # @@ -200,10 +144,8 @@ module ActionDispatch # # Example: # - # map.connect 'post/:id', :controller => 'posts', :action => 'show', - # :conditions => { :method => :get } - # map.connect 'post/:id', :controller => 'posts', :action => 'create_comment', - # :conditions => { :method => :post } + # get 'post/:id' => 'posts#show' + # post 'post/:id' => "posts#create_comment' # # Now, if you POST to /posts/:id, it will route to the create_comment action. A GET on the same # URL will route to the show action. @@ -212,7 +154,7 @@ module ActionDispatch # # You can reload routes if you feel you must: # - # ActionController::Routing::Routes.reload + # Rails::Application.reload_routes! # # This will clear all named routes and reload routes.rb if the file has been modified from # last load. To absolutely force reloading, use reload!. -- cgit v1.2.3