1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge branch 'master' of git@github.com:rails/rails

This commit is contained in:
Jeremy Kemper 2009-02-27 12:21:34 -08:00
commit df2adc4c51
55 changed files with 911 additions and 644 deletions

View file

@ -1,4 +1,4 @@
*Edge*
*2.3.1 [RC2] (February 27th, 2009)*
* Fixed that ActionMailer should send correctly formatted Return-Path in MAIL FROM for SMTP #1842 [Matt Jones]

View file

@ -55,7 +55,7 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "actionmailer"
s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 2.3.0' + PKG_BUILD)
s.add_dependency('actionpack', '= 2.3.1' + PKG_BUILD)
s.has_rdoc = true
s.requirements << 'none'

View file

@ -2,7 +2,7 @@ module ActionMailer
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 0
TINY = 1
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -1,4 +1,6 @@
*Edge*
*2.3.1 [RC2] (February 27th, 2009)*
* Fixed that passing a custom form builder would be forwarded to nested fields_for calls #2023 [Eloy Duran/Nate Wiger]
* Added partial scoping to TranslationHelper#translate, so if you call translate(".foo") from the people/index.html.erb template, you'll actually be calling I18n.translate("people.index.foo") [DHH]

View file

@ -80,7 +80,7 @@ spec = Gem::Specification.new do |s|
s.has_rdoc = true
s.requirements << 'none'
s.add_dependency('activesupport', '= 2.3.0' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD)
s.add_dependency('rack', '>= 0.9.0')
s.require_path = 'lib'

View file

@ -22,7 +22,7 @@ module ActionController #:nodoc:
attr_reader :allowed_methods
def initialize(*allowed_methods)
super("Only #{allowed_methods.to_sentence} requests are allowed.")
super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
@allowed_methods = allowed_methods
end
@ -1298,7 +1298,7 @@ module ActionController #:nodoc:
rescue ActionView::MissingTemplate => e
# Was the implicit template missing, or was it another template?
if e.path == default_template_name
raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller
raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence(:locale => :en)}", caller
else
raise e
end

View file

@ -32,7 +32,7 @@ module ActionController
# <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
# constant above, an UnknownHttpMethod exception is raised.
def request_method
@request_method ||= HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
@request_method ||= HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
end
# Returns the HTTP request \method used for action processing as a

View file

@ -2,7 +2,7 @@ module ActionPack #:nodoc:
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 0
TINY = 1
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -5,17 +5,24 @@ require 'action_view/helpers/form_tag_helper'
module ActionView
module Helpers
# Form helpers are designed to make working with models much easier compared to using just standard HTML
# elements by providing a set of methods for creating forms based on your models. This helper generates the HTML
# for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form
# is submitted (i.e., when the user hits the submit button or <tt>form.submit</tt> is called via JavaScript), the form inputs will be bundled into the <tt>params</tt> object and passed back to the controller.
# Form helpers are designed to make working with models much easier
# compared to using just standard HTML elements by providing a set of
# methods for creating forms based on your models. This helper generates
# the HTML for forms, providing a method for each sort of input
# (e.g., text, password, select, and so on). When the form is submitted
# (i.e., when the user hits the submit button or <tt>form.submit</tt> is
# called via JavaScript), the form inputs will be bundled into the
# <tt>params</tt> object and passed back to the controller.
#
# There are two types of form helpers: those that specifically work with model attributes and those that don't.
# This helper deals with those that work with model attributes; to see an example of form helpers that don't work
# with model attributes, check the ActionView::Helpers::FormTagHelper documentation.
# There are two types of form helpers: those that specifically work with
# model attributes and those that don't. This helper deals with those that
# work with model attributes; to see an example of form helpers that don't
# work with model attributes, check the ActionView::Helpers::FormTagHelper
# documentation.
#
# The core method of this helper, form_for, gives you the ability to create a form for a model instance;
# for example, let's say that you have a model <tt>Person</tt> and want to create a new instance of it:
# The core method of this helper, form_for, gives you the ability to create
# a form for a model instance; for example, let's say that you have a model
# <tt>Person</tt> and want to create a new instance of it:
#
# # Note: a @person variable will have been created in the controller.
# # For example: @person = Person.new
@ -40,17 +47,22 @@ module ActionView
# <%= submit_tag 'Create' %>
# <% end %>
#
# This example will render the <tt>people/_form</tt> partial, setting a local variable called <tt>form</tt> which references the yielded FormBuilder.
#
# The <tt>params</tt> object created when this form is submitted would look like:
# This example will render the <tt>people/_form</tt> partial, setting a
# local variable called <tt>form</tt> which references the yielded
# FormBuilder. The <tt>params</tt> object created when this form is
# submitted would look like:
#
# {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
#
# The params hash has a nested <tt>person</tt> value, which can therefore be accessed with <tt>params[:person]</tt> in the controller.
# If were editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than <tt>Person.new</tt> in the controller), the objects
# attribute values are filled into the form (e.g., the <tt>person_first_name</tt> field would have that person's first name in it).
# The params hash has a nested <tt>person</tt> value, which can therefore
# be accessed with <tt>params[:person]</tt> in the controller. If were
# editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than
# <tt>Person.new</tt> in the controller), the objects attribute values are
# filled into the form (e.g., the <tt>person_first_name</tt> field would
# have that person's first name in it).
#
# If the object name contains square brackets the id for the object will be inserted. For example:
# If the object name contains square brackets the id for the object will be
# inserted. For example:
#
# <%= text_field "person[]", "name" %>
#
@ -58,8 +70,10 @@ module ActionView
#
# <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
#
# If the helper is being used to generate a repetitive sequence of similar form elements, for example in a partial
# used by <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may come in handy. Example:
# If the helper is being used to generate a repetitive sequence of similar
# form elements, for example in a partial used by
# <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may
# come in handy. Example:
#
# <%= text_field "person", "name", "index" => 1 %>
#
@ -67,14 +81,17 @@ module ActionView
#
# <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
#
# An <tt>index</tt> option may also be passed to <tt>form_for</tt> and <tt>fields_for</tt>. This automatically applies
# the <tt>index</tt> to all the nested fields.
# An <tt>index</tt> option may also be passed to <tt>form_for</tt> and
# <tt>fields_for</tt>. This automatically applies the <tt>index</tt> to
# all the nested fields.
#
# There are also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
# link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html
# There are also methods for helping to build form tags in
# link:classes/ActionView/Helpers/FormOptionsHelper.html,
# link:classes/ActionView/Helpers/DateHelper.html, and
# link:classes/ActionView/Helpers/ActiveRecordHelper.html
module FormHelper
# Creates a form and a scope around a specific model object that is used as
# a base for questioning about values for the fields.
# Creates a form and a scope around a specific model object that is used
# as a base for questioning about values for the fields.
#
# Rails provides succinct resource-oriented form generation with +form_for+
# like this:
@ -86,13 +103,15 @@ module ActionView
# <%= f.text_field :author %><br />
# <% end %>
#
# There, +form_for+ is able to generate the rest of RESTful form parameters
# based on introspection on the record, but to understand what it does we
# need to dig first into the alternative generic usage it is based upon.
# There, +form_for+ is able to generate the rest of RESTful form
# parameters based on introspection on the record, but to understand what
# it does we need to dig first into the alternative generic usage it is
# based upon.
#
# === Generic form_for
#
# The generic way to call +form_for+ yields a form builder around a model:
# The generic way to call +form_for+ yields a form builder around a
# model:
#
# <% form_for :person, :url => { :action => "update" } do |f| %>
# <%= f.error_messages %>
@ -103,8 +122,8 @@ module ActionView
# <% end %>
#
# There, the first argument is a symbol or string with the name of the
# object the form is about, and also the name of the instance variable the
# object is stored in.
# object the form is about, and also the name of the instance variable
# the object is stored in.
#
# The form builder acts as a regular form helper that somehow carries the
# model. Thus, the idea is that
@ -137,17 +156,18 @@ module ActionView
# In any of its variants, the rightmost argument to +form_for+ is an
# optional hash of options:
#
# * <tt>:url</tt> - The URL the form is submitted to. It takes the same fields
# you pass to +url_for+ or +link_to+. In particular you may pass here a
# named route directly as well. Defaults to the current action.
# * <tt>:url</tt> - The URL the form is submitted to. It takes the same
# fields you pass to +url_for+ or +link_to+. In particular you may pass
# here a named route directly as well. Defaults to the current action.
# * <tt>:html</tt> - Optional HTML attributes for the form tag.
#
# Worth noting is that the +form_for+ tag is called in a ERb evaluation block,
# not an ERb output block. So that's <tt><% %></tt>, not <tt><%= %></tt>.
# Worth noting is that the +form_for+ tag is called in a ERb evaluation
# block, not an ERb output block. So that's <tt><% %></tt>, not
# <tt><%= %></tt>.
#
# Also note that +form_for+ doesn't create an exclusive scope. It's still
# possible to use both the stand-alone FormHelper methods and methods from
# FormTagHelper. For example:
# possible to use both the stand-alone FormHelper methods and methods
# from FormTagHelper. For example:
#
# <% form_for :person, @person, :url => { :action => "update" } do |f| %>
# First name: <%= f.text_field :first_name %>
@ -156,16 +176,16 @@ module ActionView
# Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
# <% end %>
#
# This also works for the methods in FormOptionHelper and DateHelper that are
# designed to work with an object as base, like FormOptionHelper#collection_select
# and DateHelper#datetime_select.
# This also works for the methods in FormOptionHelper and DateHelper that
# are designed to work with an object as base, like
# FormOptionHelper#collection_select and DateHelper#datetime_select.
#
# === Resource-oriented style
#
# As we said above, in addition to manually configuring the +form_for+ call,
# you can rely on automated resource identification, which will use the conventions
# and named routes of that approach. This is the preferred way to use +form_for+
# nowadays.
# As we said above, in addition to manually configuring the +form_for+
# call, you can rely on automated resource identification, which will use
# the conventions and named routes of that approach. This is the
# preferred way to use +form_for+ nowadays.
#
# For example, if <tt>@post</tt> is an existing record you want to edit
#
@ -205,8 +225,10 @@ module ActionView
#
# === Customized form builders
#
# You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers,
# then use your custom builder. For example, let's say you made a helper to automatically add labels to form inputs.
# You can also build forms using a customized FormBuilder class. Subclass
# FormBuilder and override or define some more helpers, then use your
# custom builder. For example, let's say you made a helper to
# automatically add labels to form inputs.
#
# <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
# <%= f.text_field :first_name %>
@ -219,16 +241,23 @@ module ActionView
#
# <%= render :partial => f %>
#
# The rendered template is <tt>people/_labelling_form</tt> and the local variable referencing the form builder is called <tt>labelling_form</tt>.
# The rendered template is <tt>people/_labelling_form</tt> and the local
# variable referencing the form builder is called
# <tt>labelling_form</tt>.
#
# In many cases you will want to wrap the above in another helper, so you could do something like the following:
# The custom FormBuilder class is automatically merged with the options
# of a nested fields_for call, unless it's explicitely set.
#
# In many cases you will want to wrap the above in another helper, so you
# could do something like the following:
#
# def labelled_form_for(record_or_name_or_array, *args, &proc)
# options = args.extract_options!
# form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc)
# end
#
# If you don't need to attach a form to a model instance, then check out FormTagHelper#form_tag.
# If you don't need to attach a form to a model instance, then check out
# FormTagHelper#form_tag.
def form_for(record_or_name_or_array, *args, &proc)
raise ArgumentError, "Missing block" unless block_given?
@ -910,6 +939,11 @@ module ActionView
index = ""
end
if options[:builder]
args << {} unless args.last.is_a?(Hash)
args.last[:builder] ||= options[:builder]
end
case record_or_name_or_array
when String, Symbol
if nested_attributes_association?(record_or_name_or_array)

View file

@ -1001,6 +1001,47 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
def test_form_for_with_labelled_builder_with_nested_fields_for_without_options_hash
klass = nil
form_for(:post, @post, :builder => LabelledFormBuilder) do |f|
f.fields_for(:comments, Comment.new) do |nested_fields|
klass = nested_fields.class
''
end
end
assert_equal LabelledFormBuilder, klass
end
def test_form_for_with_labelled_builder_with_nested_fields_for_with_options_hash
klass = nil
form_for(:post, @post, :builder => LabelledFormBuilder) do |f|
f.fields_for(:comments, Comment.new, :index => 'foo') do |nested_fields|
klass = nested_fields.class
''
end
end
assert_equal LabelledFormBuilder, klass
end
class LabelledFormBuilderSubclass < LabelledFormBuilder; end
def test_form_for_with_labelled_builder_with_nested_fields_for_with_custom_builder
klass = nil
form_for(:post, @post, :builder => LabelledFormBuilder) do |f|
f.fields_for(:comments, Comment.new, :builder => LabelledFormBuilderSubclass) do |nested_fields|
klass = nested_fields.class
''
end
end
assert_equal LabelledFormBuilderSubclass, klass
end
def test_form_for_with_html_options_adds_options_to_form_tag
form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\"></form>"

View file

@ -1,4 +1,4 @@
*Edge*
*2.3.1 [RC2] (February 27th, 2009)*
* Added ActiveRecord::Base.each and ActiveRecord::Base.find_in_batches for batch processing [DHH/Jamis Buck]

View file

@ -177,7 +177,7 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
s.add_dependency('activesupport', '= 2.3.0' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD)
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"

View file

@ -22,7 +22,7 @@ module ActiveRecord
through_reflection = reflection.through_reflection
source_reflection_names = reflection.source_reflection_names
source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :two_words_connector => ' or ', :last_word_connector => ', or '} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :two_words_connector => ' or ', :last_word_connector => ', or '}?")
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
end
end
@ -786,11 +786,7 @@ module ActiveRecord
# 'ORDER BY p.first_name'
def has_many(association_id, options = {}, &extension)
reflection = create_has_many_reflection(association_id, options, &extension)
configure_dependency_for_has_many(reflection)
add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
add_multiple_associated_save_callbacks(reflection.name)
add_association_callbacks(reflection.name, reflection.options)
if options[:through]
@ -872,10 +868,10 @@ module ActiveRecord
# [:source]
# Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
# inferred from the association. <tt>has_one :favorite, :through => :favorites</tt> will look for a
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
# [:source_type]
# Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
# association is a polymorphic +belongs_to+.
# association is a polymorphic +belongs_to+.
# [:readonly]
# If true, the associated object is readonly through the association.
# [:validate]
@ -898,22 +894,9 @@ module ActiveRecord
association_accessor_methods(reflection, ActiveRecord::Associations::HasOneThroughAssociation)
else
reflection = create_has_one_reflection(association_id, options)
method_name = "has_one_after_save_for_#{reflection.name}".to_sym
define_method(method_name) do
association = association_instance_get(reflection.name)
if association && (new_record? || association.new_record? || association[reflection.primary_key_name] != id)
association[reflection.primary_key_name] = id
association.save(true)
end
end
after_save method_name
add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
association_accessor_methods(reflection, HasOneAssociation)
association_constructor_method(:build, reflection, HasOneAssociation)
association_constructor_method(:create, reflection, HasOneAssociation)
configure_dependency_for_has_one(reflection)
end
end
@ -1006,40 +989,10 @@ module ActiveRecord
if reflection.options[:polymorphic]
association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym
define_method(method_name) do
association = association_instance_get(reflection.name)
if association && association.target
if association.new_record?
association.save(true)
end
if association.updated?
self[reflection.primary_key_name] = association.id
self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
end
end
end
before_save method_name
else
association_accessor_methods(reflection, BelongsToAssociation)
association_constructor_method(:build, reflection, BelongsToAssociation)
association_constructor_method(:create, reflection, BelongsToAssociation)
method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym
define_method(method_name) do
if association = association_instance_get(reflection.name)
if association.new_record?
association.save(true)
end
if association.updated?
self[reflection.primary_key_name] = association.id
end
end
end
before_save method_name
end
# Create the callbacks to update counter cache
@ -1067,8 +1020,6 @@ module ActiveRecord
)
end
add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
configure_dependency_for_belongs_to(reflection)
end
@ -1234,9 +1185,6 @@ module ActiveRecord
# 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
def has_and_belongs_to_many(association_id, options = {}, &extension)
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
add_multiple_associated_save_callbacks(reflection.name)
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
# Don't use a before_destroy callback since users' before_destroy
@ -1358,70 +1306,6 @@ module ActiveRecord
end
end
def add_single_associated_validation_callbacks(association_name)
method_name = "validate_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
if association = association_instance_get(association_name)
errors.add association_name unless association.target.nil? || association.valid?
end
end
validate method_name
end
def add_multiple_associated_validation_callbacks(association_name)
method_name = "validate_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
association = association_instance_get(association_name)
if association
if new_record?
association
elsif association.loaded?
association.select { |record| record.new_record? }
else
association.target.select { |record| record.new_record? }
end.each do |record|
errors.add association_name unless record.valid?
end
end
end
validate method_name
end
def add_multiple_associated_save_callbacks(association_name)
method_name = "before_save_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
@new_record_before_save = new_record?
true
end
before_save method_name
method_name = "after_create_or_update_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
association = association_instance_get(association_name)
records_to_save = if @new_record_before_save
association
elsif association && association.loaded?
association.select { |record| record.new_record? }
elsif association && !association.loaded?
association.target.select { |record| record.new_record? }
else
[]
end
records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank?
# reconstruct the SQL queries now that we know the owner's id
association.send(:construct_sql) if association.respond_to?(:construct_sql)
end
# Doesn't use after_save as that would save associations added in after_create/after_update twice
after_create method_name
after_update method_name
end
def association_constructor_method(constructor, reflection, association_proxy_class)
define_method("#{constructor}_#{reflection.name}") do |*params|
attributees = params.first unless params.empty?

View file

@ -28,12 +28,12 @@ module ActiveRecord
load_target.size
end
def insert_record(record, force=true)
def insert_record(record, force = true, validate = true)
if record.new_record?
if force
record.save!
else
return false unless record.save
return false unless record.save(validate)
end
end

View file

@ -56,9 +56,9 @@ module ActiveRecord
"#{@reflection.name}_count"
end
def insert_record(record)
def insert_record(record, force = false, validate = true)
set_belongs_to_association_for(record)
record.save
force ? record.save! : record.save(validate)
end
# Deletes the records according to the <tt>:dependent</tt> option.

View file

@ -47,12 +47,12 @@ module ActiveRecord
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
end
def insert_record(record, force=true)
def insert_record(record, force = true, validate = true)
if record.new_record?
if force
record.save!
else
return false unless record.save
return false unless record.save(validate)
end
end
through_reflection = @reflection.through_reflection

View file

@ -125,79 +125,63 @@ module ActiveRecord
# post.author.name = ''
# post.save(false) # => true
module AutosaveAssociation
ASSOCIATION_TYPES = %w{ has_one belongs_to has_many has_and_belongs_to_many }
def self.included(base)
base.class_eval do
base.extend(ClassMethods)
alias_method_chain :reload, :autosave_associations
alias_method_chain :save, :autosave_associations
alias_method_chain :save!, :autosave_associations
alias_method_chain :valid?, :autosave_associations
%w{ has_one belongs_to has_many has_and_belongs_to_many }.each do |type|
ASSOCIATION_TYPES.each do |type|
base.send("valid_keys_for_#{type}_association") << :autosave
end
end
end
# Saves the parent, <tt>self</tt>, and any loaded autosave associations.
# In addition, it destroys all children that were marked for destruction
# with mark_for_destruction.
#
# This all happens inside a transaction, _if_ the Transactions module is included into
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
def save_with_autosave_associations(perform_validation = true)
returning(save_without_autosave_associations(perform_validation)) do |valid|
if valid
self.class.reflect_on_all_autosave_associations.each do |reflection|
if (association = association_instance_get(reflection.name)) && association.loaded?
if association.is_a?(Array)
association.proxy_target.each do |child|
child.marked_for_destruction? ? child.destroy : child.save(perform_validation)
end
else
association.marked_for_destruction? ? association.destroy : association.save(perform_validation)
end
end
module ClassMethods
private
# def belongs_to(name, options = {})
# super
# add_autosave_association_callbacks(reflect_on_association(name))
# end
ASSOCIATION_TYPES.each do |type|
module_eval %{
def #{type}(name, options = {})
super
add_autosave_association_callbacks(reflect_on_association(name))
end
end
}
end
end
# Attempts to save the record just like save_with_autosave_associations but
# will raise a RecordInvalid exception instead of returning false if the
# record is not valid.
def save_with_autosave_associations!
if valid_with_autosave_associations?
save_with_autosave_associations(false) || raise(RecordNotSaved)
else
raise RecordInvalid.new(self)
end
end
# Adds a validate and save callback for the association as specified by
# the +reflection+.
def add_autosave_association_callbacks(reflection)
save_method = "autosave_associated_records_for_#{reflection.name}"
validation_method = "validate_associated_records_for_#{reflection.name}"
validate validation_method
# Returns whether or not the parent, <tt>self</tt>, and any loaded autosave associations are valid.
def valid_with_autosave_associations?
if valid_without_autosave_associations?
self.class.reflect_on_all_autosave_associations.all? do |reflection|
if (association = association_instance_get(reflection.name)) && association.loaded?
if association.is_a?(Array)
association.proxy_target.all? { |child| autosave_association_valid?(reflection, child) }
else
autosave_association_valid?(reflection, association)
end
else
true # association not loaded yet, so it should be valid
case reflection.macro
when :has_many, :has_and_belongs_to_many
before_save :before_save_collection_association
define_method(save_method) { save_collection_association(reflection) }
# Doesn't use after_save as that would save associations added in after_create/after_update twice
after_create save_method
after_update save_method
define_method(validation_method) { validate_collection_association(reflection) }
else
case reflection.macro
when :has_one
define_method(save_method) { save_has_one_association(reflection) }
after_save save_method
when :belongs_to
define_method(save_method) { save_belongs_to_association(reflection) }
before_save save_method
end
define_method(validation_method) { validate_single_association(reflection) }
end
else
false # self was not valid
end
end
# Returns whether or not the association is valid and applies any errors to the parent, <tt>self</tt>, if it wasn't.
def autosave_association_valid?(reflection, association)
returning(association.valid?) do |valid|
association.errors.each do |attribute, message|
errors.add "#{reflection.name}_#{attribute}", message
end unless valid
end
end
@ -221,5 +205,145 @@ module ActiveRecord
def marked_for_destruction?
@marked_for_destruction
end
private
# Returns the record for an association collection that should be validated
# or saved. If +autosave+ is +false+ only new records will be returned,
# unless the parent is/was a new record itself.
def associated_records_to_validate_or_save(association, new_record, autosave)
if new_record
association
elsif association.loaded?
autosave ? association : association.select { |record| record.new_record? }
else
autosave ? association.target : association.target.select { |record| record.new_record? }
end
end
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
# turned on for the association specified by +reflection+.
def validate_single_association(reflection)
if reflection.options[:validate] == true || reflection.options[:autosave] == true
if (association = association_instance_get(reflection.name)) && !association.target.nil?
association_valid?(reflection, association)
end
end
end
# Validate the associated records if <tt>:validate</tt> or
# <tt>:autosave</tt> is turned on for the association specified by
# +reflection+.
def validate_collection_association(reflection)
if reflection.options[:validate] != false && association = association_instance_get(reflection.name)
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
records.each { |record| association_valid?(reflection, record) }
end
end
end
# Returns whether or not the association is valid and applies any errors to
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
# enabled records if they're marked_for_destruction?.
def association_valid?(reflection, association)
unless valid = association.valid?
if reflection.options[:autosave]
unless association.marked_for_destruction?
association.errors.each do |attribute, message|
attribute = "#{reflection.name}_#{attribute}"
errors.add(attribute, message) unless errors.on(attribute)
end
end
else
errors.add(reflection.name)
end
end
valid
end
# Is used as a before_save callback to check while saving a collection
# association whether or not the parent was a new record before saving.
def before_save_collection_association
@new_record_before_save = new_record?
true
end
# Saves any new associated records, or all loaded autosave associations if
# <tt>:autosave</tt> is enabled on the association.
#
# In addition, it destroys all children that were marked for destruction
# with mark_for_destruction.
#
# This all happens inside a transaction, _if_ the Transactions module is included into
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
def save_collection_association(reflection)
if association = association_instance_get(reflection.name)
autosave = reflection.options[:autosave]
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
records.each do |record|
if autosave && record.marked_for_destruction?
record.destroy
elsif @new_record_before_save || record.new_record?
if autosave
association.send(:insert_record, record, false, false)
else
association.send(:insert_record, record)
end
elsif autosave
record.save(false)
end
end
end
# reconstruct the SQL queries now that we know the owner's id
association.send(:construct_sql) if association.respond_to?(:construct_sql)
end
end
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled
# on the association.
#
# In addition, it will destroy the association if it was marked for
# destruction with mark_for_destruction.
#
# This all happens inside a transaction, _if_ the Transactions module is included into
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
def save_has_one_association(reflection)
if association = association_instance_get(reflection.name)
if reflection.options[:autosave] && association.marked_for_destruction?
association.destroy
elsif new_record? || association.new_record? || association[reflection.primary_key_name] != id || reflection.options[:autosave]
association[reflection.primary_key_name] = id
association.save(false)
end
end
end
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled
# on the association.
#
# In addition, it will destroy the association if it was marked for
# destruction with mark_for_destruction.
#
# This all happens inside a transaction, _if_ the Transactions module is included into
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
def save_belongs_to_association(reflection)
if association = association_instance_get(reflection.name)
if reflection.options[:autosave] && association.marked_for_destruction?
association.destroy
else
association.save(false) if association.new_record? || reflection.options[:autosave]
if association.updated?
self[reflection.primary_key_name] = association.id
# TODO: Removing this code doesn't seem to matter…
if reflection.options[:polymorphic]
self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
end
end
end
end
end
end
end

View file

@ -1,11 +1,12 @@
module ActiveRecord
module NamedScope
# All subclasses of ActiveRecord::Base have two named \scopes:
# * <tt>all</tt> - which is similar to a <tt>find(:all)</tt> query, and
# All subclasses of ActiveRecord::Base have one named scope:
# * <tt>scoped</tt> - which allows for the creation of anonymous \scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt>
#
# These anonymous \scopes tend to be useful when procedurally generating complex queries, where passing
# intermediate values (scopes) around as first-class objects is convenient.
#
# You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
def self.included(base)
base.class_eval do
extend ClassMethods

View file

@ -2,7 +2,7 @@ module ActiveRecord
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 0
TINY = 1
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -190,19 +190,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
end
def test_assignment_before_parent_saved
client = Client.find(:first)
apple = Firm.new("name" => "Apple")
client.firm = apple
assert_equal apple, client.firm
assert apple.new_record?
assert client.save
assert apple.save
assert !apple.new_record?
assert_equal apple, client.firm
assert_equal apple, client.firm(true)
end
def test_assignment_before_child_saved
final_cut = Client.new("name" => "Final Cut")
firm = Firm.find(1)
@ -215,19 +202,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal firm, final_cut.firm(true)
end
def test_assignment_before_either_saved
final_cut = Client.new("name" => "Final Cut")
apple = Firm.new("name" => "Apple")
final_cut.firm = apple
assert final_cut.new_record?
assert apple.new_record?
assert final_cut.save
assert !final_cut.new_record?
assert !apple.new_record?
assert_equal apple, final_cut.firm
assert_equal apple, final_cut.firm(true)
end
def test_new_record_with_foreign_key_but_no_object
c = Client.new("firm_id" => 1)
assert_equal Firm.find(:first), c.firm_with_basic_id
@ -274,90 +248,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 17, reply.replies.size
end
def test_store_two_association_with_one_save
num_orders = Order.count
num_customers = Customer.count
order = Order.new
customer1 = order.billing = Customer.new
customer2 = order.shipping = Customer.new
assert order.save
assert_equal customer1, order.billing
assert_equal customer2, order.shipping
order.reload
assert_equal customer1, order.billing
assert_equal customer2, order.shipping
assert_equal num_orders +1, Order.count
assert_equal num_customers +2, Customer.count
end
def test_store_association_in_two_relations_with_one_save
num_orders = Order.count
num_customers = Customer.count
order = Order.new
customer = order.billing = order.shipping = Customer.new
assert order.save
assert_equal customer, order.billing
assert_equal customer, order.shipping
order.reload
assert_equal customer, order.billing
assert_equal customer, order.shipping
assert_equal num_orders +1, Order.count
assert_equal num_customers +1, Customer.count
end
def test_store_association_in_two_relations_with_one_save_in_existing_object
num_orders = Order.count
num_customers = Customer.count
order = Order.create
customer = order.billing = order.shipping = Customer.new
assert order.save
assert_equal customer, order.billing
assert_equal customer, order.shipping
order.reload
assert_equal customer, order.billing
assert_equal customer, order.shipping
assert_equal num_orders +1, Order.count
assert_equal num_customers +1, Customer.count
end
def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values
num_orders = Order.count
num_customers = Customer.count
order = Order.create
customer = order.billing = order.shipping = Customer.new
assert order.save
assert_equal customer, order.billing
assert_equal customer, order.shipping
order.reload
customer = order.billing = order.shipping = Customer.new
assert order.save
order.reload
assert_equal customer, order.billing
assert_equal customer, order.shipping
assert_equal num_orders +1, Order.count
assert_equal num_customers +2, Customer.count
end
def test_association_assignment_sticks
post = Post.find(:first)
@ -410,25 +300,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal nil, sponsor.sponsorable_id
end
def test_save_fails_for_invalid_belongs_to
assert log = AuditLog.create(:developer_id=>0,:message=>"")
log.developer = Developer.new
assert !log.developer.valid?
assert !log.valid?
assert !log.save
assert_equal "is invalid", log.errors.on("developer")
end
def test_save_succeeds_for_invalid_belongs_to_with_validate_false
assert log = AuditLog.create(:developer_id=>0,:message=>"")
log.unvalidated_developer = Developer.new
assert !log.unvalidated_developer.valid?
assert log.valid?
assert log.save
end
def test_belongs_to_proxy_should_not_respond_to_private_methods
assert_raises(NoMethodError) { companies(:first_firm).private_method }
assert_raises(NoMethodError) { companies(:second_client).firm.private_method }

View file

@ -317,81 +317,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, companies(:first_firm).clients_of_firm(true).size
end
def test_adding_before_save
no_of_firms = Firm.count
no_of_clients = Client.count
new_firm = Firm.new("name" => "A New Firm, Inc")
c = Client.new("name" => "Apple")
new_firm.clients_of_firm.push Client.new("name" => "Natural Company")
assert_equal 1, new_firm.clients_of_firm.size
new_firm.clients_of_firm << c
assert_equal 2, new_firm.clients_of_firm.size
assert_equal no_of_firms, Firm.count # Firm was not saved to database.
assert_equal no_of_clients, Client.count # Clients were not saved to database.
assert new_firm.save
assert !new_firm.new_record?
assert !c.new_record?
assert_equal new_firm, c.firm
assert_equal no_of_firms+1, Firm.count # Firm was saved to database.
assert_equal no_of_clients+2, Client.count # Clients were saved to database.
assert_equal 2, new_firm.clients_of_firm.size
assert_equal 2, new_firm.clients_of_firm(true).size
end
def test_invalid_adding
firm = Firm.find(1)
assert !(firm.clients_of_firm << c = Client.new)
assert c.new_record?
assert !firm.valid?
assert !firm.save
assert c.new_record?
end
def test_invalid_adding_before_save
no_of_firms = Firm.count
no_of_clients = Client.count
new_firm = Firm.new("name" => "A New Firm, Inc")
new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
assert c.new_record?
assert !c.valid?
assert !new_firm.valid?
assert !new_firm.save
assert c.new_record?
assert new_firm.new_record?
end
def test_invalid_adding_with_validate_false
firm = Firm.find(:first)
client = Client.new
firm.unvalidated_clients_of_firm << client
assert firm.valid?
assert !client.valid?
assert firm.save
assert client.new_record?
end
def test_valid_adding_with_validate_false
no_of_clients = Client.count
firm = Firm.find(:first)
client = Client.new("name" => "Apple")
assert firm.valid?
assert client.valid?
assert client.new_record?
firm.unvalidated_clients_of_firm << client
assert firm.save
assert !client.new_record?
assert_equal no_of_clients+1, Client.count
end
def test_build
company = companies(:first_firm)
new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
@ -400,10 +325,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "Another Client", new_client.name
assert new_client.new_record?
assert_equal new_client, company.clients_of_firm.last
company.name += '-changed'
assert_queries(2) { assert company.save }
assert !new_client.new_record?
assert_equal 2, company.clients_of_firm(true).size
end
def test_collection_size_after_building
@ -428,11 +349,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_many
company = companies(:first_firm)
new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
assert_equal 2, new_clients.size
company.name += '-changed'
assert_queries(3) { assert company.save }
assert_equal 3, company.clients_of_firm(true).size
end
def test_build_followed_by_save_does_not_load_target
@ -463,10 +380,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "Another Client", new_client.name
assert new_client.new_record?
assert_equal new_client, company.clients_of_firm.last
company.name += '-changed'
assert_queries(2) { assert company.save }
assert !new_client.new_record?
assert_equal 2, company.clients_of_firm(true).size
end
def test_build_many_via_block
@ -480,10 +393,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, new_clients.size
assert_equal "changed", new_clients.first.name
assert_equal "changed", new_clients.last.name
company.name += '-changed'
assert_queries(3) { assert company.save }
assert_equal 3, company.clients_of_firm(true).size
end
def test_create_without_loading_association
@ -501,16 +410,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, first_firm.clients_of_firm.size
end
def test_invalid_build
new_client = companies(:first_firm).clients_of_firm.build
assert new_client.new_record?
assert !new_client.valid?
assert_equal new_client, companies(:first_firm).clients_of_firm.last
assert !companies(:first_firm).save
assert new_client.new_record?
assert_equal 1, companies(:first_firm).clients_of_firm(true).size
end
def test_create
force_signal37_to_load_all_clients_of_firm
new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")
@ -843,15 +742,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !firm.clients.include?(:first_client)
end
def test_replace_on_new_object
firm = Firm.new("name" => "New Firm")
firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
assert firm.save
firm.reload
assert_equal 2, firm.clients.length
assert firm.clients.include?(Client.find_by_name("New Client"))
end
def test_get_ids
assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids
end
@ -879,15 +769,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert company.clients_using_sql.loaded?
end
def test_assign_ids
firm = Firm.new("name" => "Apple")
firm.client_ids = [companies(:first_client).id, companies(:second_client).id]
firm.save
firm.reload
assert_equal 2, firm.clients.length
assert firm.clients.include?(companies(:second_client))
end
def test_assign_ids_ignoring_blanks
firm = Firm.create!(:name => 'Apple')
firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, '']
@ -910,16 +791,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection, &block) }
end
def test_assign_ids_for_through_a_belongs_to
post = Post.new(:title => "Assigning IDs works!", :body => "You heared it here first, folks!")
post.person_ids = [people(:david).id, people(:michael).id]
post.save
post.reload
assert_equal 2, post.people.length
assert post.people.include?(people(:david))
end
def test_dynamic_find_should_respect_association_order_for_through
assert_equal Comment.find(10), authors(:david).comments_desc.find(:first, :conditions => "comments.type = 'SpecialComment'")
assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type('SpecialComment')

View file

@ -193,28 +193,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.account
end
def test_build_before_child_saved
firm = Firm.find(1)
account = firm.account.build("credit_limit" => 1000)
assert_equal account, firm.account
assert account.new_record?
assert firm.save
assert_equal account, firm.account
assert !account.new_record?
end
def test_build_before_either_saved
firm = Firm.new("name" => "GlobalMegaCorp")
firm.account = account = Account.new("credit_limit" => 1000)
assert_equal account, firm.account
assert account.new_record?
assert firm.save
assert_equal account, firm.account
assert !account.new_record?
end
def test_failing_build_association
firm = Firm.new("name" => "GlobalMegaCorp")
firm.save
@ -253,16 +231,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
firm.destroy
end
def test_assignment_before_parent_saved
firm = Firm.new("name" => "GlobalMegaCorp")
firm.account = a = Account.find(1)
assert firm.new_record?
assert_equal a, firm.account
assert firm.save
assert_equal a, firm.account
assert_equal a, firm.account(true)
end
def test_finding_with_interpolated_condition
firm = Firm.find(:first)
superior = firm.clients.create(:name => 'SuperiorCo')
@ -279,61 +247,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal a, firm.account
assert_equal a, firm.account(true)
end
def test_save_fails_for_invalid_has_one
firm = Firm.find(:first)
assert firm.valid?
firm.account = Account.new
assert !firm.account.valid?
assert !firm.valid?
assert !firm.save
assert_equal "is invalid", firm.errors.on("account")
end
def test_save_succeeds_for_invalid_has_one_with_validate_false
firm = Firm.find(:first)
assert firm.valid?
firm.unvalidated_account = Account.new
assert !firm.unvalidated_account.valid?
assert firm.valid?
assert firm.save
end
def test_assignment_before_either_saved
firm = Firm.new("name" => "GlobalMegaCorp")
firm.account = a = Account.new("credit_limit" => 1000)
assert firm.new_record?
assert a.new_record?
assert_equal a, firm.account
assert firm.save
assert !firm.new_record?
assert !a.new_record?
assert_equal a, firm.account
assert_equal a, firm.account(true)
end
def test_not_resaved_when_unchanged
firm = Firm.find(:first, :include => :account)
firm.name += '-changed'
assert_queries(1) { firm.save! }
firm = Firm.find(:first)
firm.account = Account.find(:first)
assert_queries(Firm.partial_updates? ? 0 : 1) { firm.save! }
firm = Firm.find(:first).clone
firm.account = Account.find(:first)
assert_queries(2) { firm.save! }
firm = Firm.find(:first).clone
firm.account = Account.find(:first).clone
assert_queries(2) { firm.save! }
end
def test_save_still_works_after_accessing_nil_has_one
jp = Company.new :name => 'Jaded Pixel'

View file

@ -1,10 +1,17 @@
require "cases/helper"
require "models/pirate"
require "models/ship"
require "models/ship_part"
require "models/bird"
require "models/parrot"
require "models/treasure"
require 'cases/helper'
require 'models/bird'
require 'models/company'
require 'models/customer'
require 'models/developer'
require 'models/order'
require 'models/parrot'
require 'models/person'
require 'models/pirate'
require 'models/post'
require 'models/reader'
require 'models/ship'
require 'models/ship_part'
require 'models/treasure'
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
def test_autosave_should_be_a_valid_option_for_has_one
@ -30,6 +37,383 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
end
end
class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
def test_save_fails_for_invalid_has_one
firm = Firm.find(:first)
assert firm.valid?
firm.account = Account.new
assert !firm.account.valid?
assert !firm.valid?
assert !firm.save
assert_equal "is invalid", firm.errors.on("account")
end
def test_save_succeeds_for_invalid_has_one_with_validate_false
firm = Firm.find(:first)
assert firm.valid?
firm.unvalidated_account = Account.new
assert !firm.unvalidated_account.valid?
assert firm.valid?
assert firm.save
end
def test_build_before_child_saved
firm = Firm.find(1)
account = firm.account.build("credit_limit" => 1000)
assert_equal account, firm.account
assert account.new_record?
assert firm.save
assert_equal account, firm.account
assert !account.new_record?
end
def test_build_before_either_saved
firm = Firm.new("name" => "GlobalMegaCorp")
firm.account = account = Account.new("credit_limit" => 1000)
assert_equal account, firm.account
assert account.new_record?
assert firm.save
assert_equal account, firm.account
assert !account.new_record?
end
def test_assignment_before_parent_saved
firm = Firm.new("name" => "GlobalMegaCorp")
firm.account = a = Account.find(1)
assert firm.new_record?
assert_equal a, firm.account
assert firm.save
assert_equal a, firm.account
assert_equal a, firm.account(true)
end
def test_assignment_before_either_saved
firm = Firm.new("name" => "GlobalMegaCorp")
firm.account = a = Account.new("credit_limit" => 1000)
assert firm.new_record?
assert a.new_record?
assert_equal a, firm.account
assert firm.save
assert !firm.new_record?
assert !a.new_record?
assert_equal a, firm.account
assert_equal a, firm.account(true)
end
def test_not_resaved_when_unchanged
firm = Firm.find(:first, :include => :account)
firm.name += '-changed'
assert_queries(1) { firm.save! }
firm = Firm.find(:first)
firm.account = Account.find(:first)
assert_queries(Firm.partial_updates? ? 0 : 1) { firm.save! }
firm = Firm.find(:first).clone
firm.account = Account.find(:first)
assert_queries(2) { firm.save! }
firm = Firm.find(:first).clone
firm.account = Account.find(:first).clone
assert_queries(2) { firm.save! }
end
end
class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
def test_save_fails_for_invalid_belongs_to
assert log = AuditLog.create(:developer_id => 0, :message => "")
log.developer = Developer.new
assert !log.developer.valid?
assert !log.valid?
assert !log.save
assert_equal "is invalid", log.errors.on("developer")
end
def test_save_succeeds_for_invalid_belongs_to_with_validate_false
assert log = AuditLog.create(:developer_id => 0, :message=> "")
log.unvalidated_developer = Developer.new
assert !log.unvalidated_developer.valid?
assert log.valid?
assert log.save
end
def test_assignment_before_parent_saved
client = Client.find(:first)
apple = Firm.new("name" => "Apple")
client.firm = apple
assert_equal apple, client.firm
assert apple.new_record?
assert client.save
assert apple.save
assert !apple.new_record?
assert_equal apple, client.firm
assert_equal apple, client.firm(true)
end
def test_assignment_before_either_saved
final_cut = Client.new("name" => "Final Cut")
apple = Firm.new("name" => "Apple")
final_cut.firm = apple
assert final_cut.new_record?
assert apple.new_record?
assert final_cut.save
assert !final_cut.new_record?
assert !apple.new_record?
assert_equal apple, final_cut.firm
assert_equal apple, final_cut.firm(true)
end
def test_store_two_association_with_one_save
num_orders = Order.count
num_customers = Customer.count
order = Order.new
customer1 = order.billing = Customer.new
customer2 = order.shipping = Customer.new
assert order.save
assert_equal customer1, order.billing
assert_equal customer2, order.shipping
order.reload
assert_equal customer1, order.billing
assert_equal customer2, order.shipping
assert_equal num_orders +1, Order.count
assert_equal num_customers +2, Customer.count
end
def test_store_association_in_two_relations_with_one_save
num_orders = Order.count
num_customers = Customer.count
order = Order.new
customer = order.billing = order.shipping = Customer.new
assert order.save
assert_equal customer, order.billing
assert_equal customer, order.shipping
order.reload
assert_equal customer, order.billing
assert_equal customer, order.shipping
assert_equal num_orders +1, Order.count
assert_equal num_customers +1, Customer.count
end
def test_store_association_in_two_relations_with_one_save_in_existing_object
num_orders = Order.count
num_customers = Customer.count
order = Order.create
customer = order.billing = order.shipping = Customer.new
assert order.save
assert_equal customer, order.billing
assert_equal customer, order.shipping
order.reload
assert_equal customer, order.billing
assert_equal customer, order.shipping
assert_equal num_orders +1, Order.count
assert_equal num_customers +1, Customer.count
end
def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values
num_orders = Order.count
num_customers = Customer.count
order = Order.create
customer = order.billing = order.shipping = Customer.new
assert order.save
assert_equal customer, order.billing
assert_equal customer, order.shipping
order.reload
customer = order.billing = order.shipping = Customer.new
assert order.save
order.reload
assert_equal customer, order.billing
assert_equal customer, order.shipping
assert_equal num_orders +1, Order.count
assert_equal num_customers +2, Customer.count
end
end
class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
fixtures :companies, :people
def test_invalid_adding
firm = Firm.find(1)
assert !(firm.clients_of_firm << c = Client.new)
assert c.new_record?
assert !firm.valid?
assert !firm.save
assert c.new_record?
end
def test_invalid_adding_before_save
no_of_firms = Firm.count
no_of_clients = Client.count
new_firm = Firm.new("name" => "A New Firm, Inc")
new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
assert c.new_record?
assert !c.valid?
assert !new_firm.valid?
assert !new_firm.save
assert c.new_record?
assert new_firm.new_record?
end
def test_invalid_adding_with_validate_false
firm = Firm.find(:first)
client = Client.new
firm.unvalidated_clients_of_firm << client
assert firm.valid?
assert !client.valid?
assert firm.save
assert client.new_record?
end
def test_valid_adding_with_validate_false
no_of_clients = Client.count
firm = Firm.find(:first)
client = Client.new("name" => "Apple")
assert firm.valid?
assert client.valid?
assert client.new_record?
firm.unvalidated_clients_of_firm << client
assert firm.save
assert !client.new_record?
assert_equal no_of_clients+1, Client.count
end
def test_invalid_build
new_client = companies(:first_firm).clients_of_firm.build
assert new_client.new_record?
assert !new_client.valid?
assert_equal new_client, companies(:first_firm).clients_of_firm.last
assert !companies(:first_firm).save
assert new_client.new_record?
assert_equal 1, companies(:first_firm).clients_of_firm(true).size
end
def test_adding_before_save
no_of_firms = Firm.count
no_of_clients = Client.count
new_firm = Firm.new("name" => "A New Firm, Inc")
c = Client.new("name" => "Apple")
new_firm.clients_of_firm.push Client.new("name" => "Natural Company")
assert_equal 1, new_firm.clients_of_firm.size
new_firm.clients_of_firm << c
assert_equal 2, new_firm.clients_of_firm.size
assert_equal no_of_firms, Firm.count # Firm was not saved to database.
assert_equal no_of_clients, Client.count # Clients were not saved to database.
assert new_firm.save
assert !new_firm.new_record?
assert !c.new_record?
assert_equal new_firm, c.firm
assert_equal no_of_firms+1, Firm.count # Firm was saved to database.
assert_equal no_of_clients+2, Client.count # Clients were saved to database.
assert_equal 2, new_firm.clients_of_firm.size
assert_equal 2, new_firm.clients_of_firm(true).size
end
def test_assign_ids
firm = Firm.new("name" => "Apple")
firm.client_ids = [companies(:first_client).id, companies(:second_client).id]
firm.save
firm.reload
assert_equal 2, firm.clients.length
assert firm.clients.include?(companies(:second_client))
end
def test_assign_ids_for_through_a_belongs_to
post = Post.new(:title => "Assigning IDs works!", :body => "You heared it here first, folks!")
post.person_ids = [people(:david).id, people(:michael).id]
post.save
post.reload
assert_equal 2, post.people.length
assert post.people.include?(people(:david))
end
def test_build_before_save
company = companies(:first_firm)
new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
assert !company.clients_of_firm.loaded?
company.name += '-changed'
assert_queries(2) { assert company.save }
assert !new_client.new_record?
assert_equal 2, company.clients_of_firm(true).size
end
def test_build_many_before_save
company = companies(:first_firm)
new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
company.name += '-changed'
assert_queries(3) { assert company.save }
assert_equal 3, company.clients_of_firm(true).size
end
def test_build_via_block_before_save
company = companies(:first_firm)
new_client = assert_no_queries { company.clients_of_firm.build {|client| client.name = "Another Client" } }
assert !company.clients_of_firm.loaded?
company.name += '-changed'
assert_queries(2) { assert company.save }
assert !new_client.new_record?
assert_equal 2, company.clients_of_firm(true).size
end
def test_build_many_via_block_before_save
company = companies(:first_firm)
new_clients = assert_no_queries do
company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client|
client.name = "changed"
end
end
company.name += '-changed'
assert_queries(3) { assert company.save }
assert_equal 3, company.clients_of_firm(true).size
end
def test_replace_on_new_object
firm = Firm.new("name" => "New Firm")
firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
assert firm.save
firm.reload
assert_equal 2, firm.clients.length
assert firm.clients.include?(Client.find_by_name("New Client"))
end
end
class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
self.use_transactional_fixtures = false
@ -62,6 +446,14 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
assert_nil Ship.find_by_id(id)
end
def test_should_skip_validation_on_a_child_association_if_marked_for_destruction
@pirate.ship.name = ''
assert !@pirate.valid?
@pirate.ship.mark_for_destruction
assert_difference('Ship.count', -1) { @pirate.save! }
end
def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_child
# Stub the save method of the @pirate.ship instance to destroy and then raise an exception
class << @pirate.ship
@ -91,6 +483,14 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
assert_nil Pirate.find_by_id(id)
end
def test_should_skip_validation_on_a_parent_association_if_marked_for_destruction
@ship.pirate.catchphrase = ''
assert !@ship.valid?
@ship.pirate.mark_for_destruction
assert_difference('Pirate.count', -1) { @ship.save! }
end
def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_parent
# Stub the save method of the @ship.pirate instance to destroy and then raise an exception
class << @ship.pirate
@ -124,6 +524,17 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
ids.each { |id| assert_nil klass.find_by_id(id) }
end
define_method("test_should_skip_validation_on_the_#{association_name}_association_if_marked_for_destruction") do
2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") }
children = @pirate.send(association_name)
children.each { |child| child.name = '' }
assert !@pirate.valid?
children.each { |child| child.mark_for_destruction }
assert_difference("#{association_name.classify}.count", -2) { @pirate.save! }
end
define_method("test_should_rollback_destructions_if_an_exception_occurred_while_saving_#{association_name}") do
2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") }
before = @pirate.send(association_name).map { |c| c }
@ -181,6 +592,14 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
assert !@pirate.errors.on(:ship_name).blank?
end
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
@pirate.ship.name = nil
@pirate.catchphrase = nil
assert !@pirate.valid?
assert !@pirate.errors.on(:ship_name).blank?
assert !@pirate.errors.on(:catchphrase).blank?
end
def test_should_still_allow_to_bypass_validations_on_the_associated_model
@pirate.catchphrase = ''
@pirate.ship.name = ''
@ -263,6 +682,14 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
assert !@ship.errors.on(:pirate_catchphrase).blank?
end
def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid
@ship.name = nil
@ship.pirate.catchphrase = nil
assert !@ship.valid?
assert !@ship.errors.on(:name).blank?
assert !@ship.errors.on(:pirate_catchphrase).blank?
end
def test_should_still_allow_to_bypass_validations_on_the_associated_model
@ship.pirate.catchphrase = ''
@ship.name = ''
@ -326,7 +753,24 @@ module AutosaveAssociationOnACollectionAssociationTests
assert @pirate.errors.on(@association_name).blank?
end
def test_should_still_allow_to_bypass_validations_on_the_associated_models
def test_should_not_use_default_invalid_error_on_associated_models
@pirate.send(@association_name).build(:name => '')
assert !@pirate.valid?
assert_equal "can't be blank", @pirate.errors.on("#{@association_name}_name")
assert @pirate.errors.on(@association_name).blank?
end
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
@pirate.send(@association_name).each { |child| child.name = '' }
@pirate.catchphrase = nil
assert !@pirate.valid?
assert_equal "can't be blank", @pirate.errors.on("#{@association_name}_name")
assert !@pirate.errors.on(:catchphrase).blank?
end
def test_should_allow_to_bypass_validations_on_the_associated_models_on_update
@pirate.catchphrase = ''
@pirate.send(@association_name).each { |child| child.name = '' }
@ -338,6 +782,20 @@ module AutosaveAssociationOnACollectionAssociationTests
]
end
def test_should_validation_the_associated_models_on_create
assert_no_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count") do
2.times { @pirate.send(@association_name).build }
@pirate.save(true)
end
end
def test_should_allow_to_bypass_validations_on_the_associated_models_on_create
assert_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count", +2) do
2.times { @pirate.send(@association_name).build }
@pirate.save(false)
end
end
def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
before = [@pirate.catchphrase, *@pirate.send(@association_name).map(&:name)]
new_names = ['Grace OMalley', 'Privateers Greed']

View file

@ -1,3 +1,8 @@
*2.3.1 [RC2] (February 27th, 2009)*
* Nothing new, just included in 2.3.1
*2.3.0 [RC1] (February 1st, 2009)*
* Nothing new, just included in 2.3.0

View file

@ -67,7 +67,7 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
s.add_dependency('activesupport', '= 2.3.0' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD)
s.require_path = 'lib'
s.autorequire = 'active_resource'

View file

@ -2,7 +2,7 @@ module ActiveResource
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 0
TINY = 1
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -1,4 +1,6 @@
*Edge*
*2.3.1 [RC2] (February 27th, 2009)*
* Vendorize i18n 0.1.3 gem (fixes issues with incompatible character encodings in Ruby 1.9) #2038 [Akira Matsuda]
* Update bundled memcache-client from 1.5.0.5 to 1.6.4.99. See http://www.mikeperham.com/2009/02/15/memcache-client-performance/ [Mike Perham]

View file

@ -7,10 +7,9 @@ module ActiveSupport #:nodoc:
# * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
# * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
def to_sentence(options = {})
default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale])
default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale])
default_two_words_connector = I18n.translate(:'support.array.two_words_connector', :locale => options[:locale])
default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale])
default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale])
# Try to emulate to_senteces previous to 2.3
if options.has_key?(:connector) || options.has_key?(:skip_last_comma)

View file

@ -70,7 +70,7 @@ module ActiveSupport
[:years, :months, :days, :minutes, :seconds].map do |length|
n = consolidated[length]
"#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero?
end.compact.to_sentence
end.compact.to_sentence(:locale => :en)
end
protected

View file

@ -22,8 +22,8 @@ end
# TODO I18n gem has not been released yet
# begin
# gem 'i18n', '~> 0.1.1'
# gem 'i18n', '~> 0.1.3'
# rescue Gem::LoadError
$:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.1.1/lib"
$:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.1.3/lib"
require 'i18n'
# end

View file

@ -1,3 +0,0 @@
.DS_Store
test/rails/fixtures
doc

View file

@ -1,7 +1,7 @@
Gem::Specification.new do |s|
s.name = "i18n"
s.version = "0.1.1"
s.date = "2008-10-26"
s.version = "0.1.3"
s.date = "2009-01-09"
s.summary = "Internationalization support for Ruby"
s.email = "rails-i18n@googlegroups.com"
s.homepage = "http://rails-i18n.org"

View file

@ -151,12 +151,7 @@ module I18n
def interpolate(locale, string, values = {})
return string unless string.is_a?(String)
if string.respond_to?(:force_encoding)
original_encoding = string.encoding
string.force_encoding(Encoding::BINARY)
end
result = string.gsub(MATCH) do
string.gsub(MATCH) do
escaped, pattern, key = $1, $2, $2.to_sym
if escaped
@ -169,9 +164,6 @@ module I18n
values[key].to_s
end
end
result.force_encoding(original_encoding) if original_encoding
result
end
# Loads a single translations file by delegating to #load_rb or

View file

@ -253,6 +253,32 @@ class I18nSimpleBackendInterpolateTest < Test::Unit::TestCase
assert_equal 'Häi David!', @backend.send(:interpolate, nil, 'Häi {{name}}!', :name => 'David')
end
def test_interpolate_given_an_unicode_value_hash_interpolates_to_the_string
assert_equal 'Hi ゆきひろ!', @backend.send(:interpolate, nil, 'Hi {{name}}!', :name => 'ゆきひろ')
end
def test_interpolate_given_an_unicode_value_hash_interpolates_into_unicode_string
assert_equal 'こんにちは、ゆきひろさん!', @backend.send(:interpolate, nil, 'こんにちは、{{name}}さん!', :name => 'ゆきひろ')
end
if Kernel.const_defined?(:Encoding)
def test_interpolate_given_a_non_unicode_multibyte_value_hash_interpolates_into_a_string_with_the_same_encoding
assert_equal euc_jp('Hi ゆきひろ!'), @backend.send(:interpolate, nil, 'Hi {{name}}!', :name => euc_jp('ゆきひろ'))
end
def test_interpolate_given_an_unicode_value_hash_into_a_non_unicode_multibyte_string_raises_encoding_compatibility_error
assert_raises(Encoding::CompatibilityError) do
@backend.send(:interpolate, nil, euc_jp('こんにちは、{{name}}さん!'), :name => 'ゆきひろ')
end
end
def test_interpolate_given_a_non_unicode_multibyte_value_hash_into_an_unicode_string_raises_encoding_compatibility_error
assert_raises(Encoding::CompatibilityError) do
@backend.send(:interpolate, nil, 'こんにちは、{{name}}さん!', :name => euc_jp('ゆきひろ'))
end
end
end
def test_interpolate_given_nil_as_a_string_returns_nil
assert_nil @backend.send(:interpolate, nil, nil, :name => 'David')
end
@ -272,6 +298,12 @@ class I18nSimpleBackendInterpolateTest < Test::Unit::TestCase
def test_interpolate_given_a_string_containing_a_reserved_key_raises_reserved_interpolation_key
assert_raises(I18n::ReservedInterpolationKey) { @backend.send(:interpolate, nil, '{{default}}', {:default => nil}) }
end
private
def euc_jp(string)
string.encode!(Encoding::EUC_JP)
end
end
class I18nSimpleBackendLocalizeDateTest < Test::Unit::TestCase
@ -485,6 +517,10 @@ end
class I18nSimpleBackendLoadPathTest < Test::Unit::TestCase
include I18nSimpleBackendTestSetup
def teardown
I18n.load_path = []
end
def test_nested_load_paths_do_not_break_locale_loading
@backend = I18n::Backend::Simple.new
I18n.load_path = [[File.dirname(__FILE__) + '/locale/en.yml']]
@ -492,6 +528,14 @@ class I18nSimpleBackendLoadPathTest < Test::Unit::TestCase
assert_nothing_raised { @backend.send :init_translations }
assert_not_nil backend_get_translations
end
def test_adding_arrays_of_filenames_to_load_path_do_not_break_locale_loading
@backend = I18n::Backend::Simple.new
I18n.load_path << Dir[File.dirname(__FILE__) + '/locale/*.{rb,yml}']
assert_nil backend_get_translations
assert_nothing_raised { @backend.send :init_translations }
assert_not_nil backend_get_translations
end
end
class I18nSimpleBackendReloadTranslationsTest < Test::Unit::TestCase
@ -521,4 +565,4 @@ class I18nSimpleBackendReloadTranslationsTest < Test::Unit::TestCase
@backend.reload!
assert_equal @backend.initialized?, false
end
end
end

View file

@ -2,7 +2,7 @@ module ActiveSupport
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 0
TINY = 1
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -1,5 +1,12 @@
*2.3.1 [RC2] (February 27th, 2009)*
* Allow metal to live in plugins #2045 [Matthew Rudy]
*2.3.0 [RC1] (February 1st, 2009)*
* Added metal [Josh Peek]
* Remove script/performance/request in favour of the performance integration tests. [Pratik Naik]
To continue using script/performance/request, install the request_profiler plugin :

View file

@ -311,11 +311,11 @@ spec = Gem::Specification.new do |s|
EOF
s.add_dependency('rake', '>= 0.8.3')
s.add_dependency('activesupport', '= 2.3.0' + PKG_BUILD)
s.add_dependency('activerecord', '= 2.3.0' + PKG_BUILD)
s.add_dependency('actionpack', '= 2.3.0' + PKG_BUILD)
s.add_dependency('actionmailer', '= 2.3.0' + PKG_BUILD)
s.add_dependency('activeresource', '= 2.3.0' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD)
s.add_dependency('activerecord', '= 2.3.1' + PKG_BUILD)
s.add_dependency('actionpack', '= 2.3.1' + PKG_BUILD)
s.add_dependency('actionmailer', '= 2.3.1' + PKG_BUILD)
s.add_dependency('activeresource', '= 2.3.1' + PKG_BUILD)
s.rdoc_options << '--exclude' << '.'
s.has_rdoc = false

View file

@ -559,6 +559,8 @@ Run `rake gems:install` to install the missing gems.
end
def initialize_metal
Rails::Rack::Metal.metal_paths += plugin_loader.engine_metal_paths
configuration.middleware.insert_before(
:"ActionController::RewindableInput",
Rails::Rack::Metal, :if => Rails::Rack::Metal.metals.any?)

View file

@ -80,6 +80,10 @@ module Rails
File.join(directory, 'app', 'controllers')
end
def metal_path
File.join(directory, 'app', 'metal')
end
def routing_file
File.join(directory, 'config', 'routes.rb')
end
@ -100,7 +104,7 @@ module Rails
def app_paths
[ File.join(directory, 'app', 'models'), File.join(directory, 'app', 'helpers'), controller_path ]
[ File.join(directory, 'app', 'models'), File.join(directory, 'app', 'helpers'), controller_path, metal_path ]
end
def lib_path
@ -160,4 +164,4 @@ module Rails
File.join(directory, 'rails', 'init.rb')
end
end
end
end

View file

@ -65,6 +65,9 @@ module Rails
$LOAD_PATH.uniq!
end
def engine_metal_paths
engines.collect(&:metal_path)
end
protected
def configure_engines
@ -178,11 +181,11 @@ module Rails
if explicit_plugin_loading_order?
if configuration.plugins.detect {|plugin| plugin != :all && !loaded?(plugin) }
missing_plugins = configuration.plugins - (plugins.map{|p| p.name.to_sym} + [:all])
raise LoadError, "Could not locate the following plugins: #{missing_plugins.to_sentence}"
raise LoadError, "Could not locate the following plugins: #{missing_plugins.to_sentence(:locale => :en)}"
end
end
end
end
end
end
end

View file

@ -6,14 +6,17 @@ module Rails
NotFoundResponse = [404, {}, []].freeze
NotFound = lambda { NotFoundResponse }
def self.metals
base = "#{Rails.root}/app/metal"
matcher = /\A#{Regexp.escape(base)}\/(.*)\.rb\Z/
cattr_accessor :metal_paths
self.metal_paths = ["#{Rails.root}/app/metal"]
Dir["#{base}/**/*.rb"].sort.map do |file|
file.sub!(matcher, '\1')
require file
file.classify.constantize
def self.metals
matcher = /#{Regexp.escape('/app/metal/')}(.*)\.rb\Z/
metal_glob = metal_paths.map{ |base| "#{base}/**/*.rb" }
Dir[*metal_glob].sort.map do |file|
path = file.match(matcher)[1]
require path
path.classify.constantize
end
end

View file

@ -2,7 +2,7 @@ module Rails
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 0
TINY = 1
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -48,7 +48,7 @@ task :test do
task
end
end.compact
abort "Errors running #{errors.to_sentence}!" if errors.any?
abort "Errors running #{errors.to_sentence(:locale => :en)}!" if errors.any?
end
namespace :test do

View file

@ -0,0 +1,10 @@
class EngineMetal
def self.call(env)
if env["PATH_INFO"] =~ /^\/metal/
[200, {"Content-Type" => "text/html"}, ["Engine metal"]]
else
[404, {"Content-Type" => "text/html"}, ["Not Found"]]
end
end
end

View file

@ -120,7 +120,7 @@ class TestPluginLoader < Test::Unit::TestCase
@loader.add_plugin_load_paths
%w( models controllers helpers ).each do |app_part|
%w( models controllers metal helpers ).each do |app_part|
assert ActiveSupport::Dependencies.load_paths.include?(
File.join(plugin_fixture_path('engines/engine'), 'app', app_part)
), "Couldn't find #{app_part} in load path"
@ -161,4 +161,4 @@ class TestPluginLoader < Test::Unit::TestCase
$LOAD_PATH.clear
ORIGINAL_LOAD_PATH.each { |path| $LOAD_PATH << path }
end
end
end