aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/examples/very_simple.rb50
-rw-r--r--actionpack/examples/views/layouts/alt.html.erb1
-rw-r--r--actionpack/examples/views/layouts/kaigi.html.erb1
-rw-r--r--actionpack/examples/views/template.html.erb1
-rw-r--r--actionpack/lib/action_controller.rb16
-rw-r--r--actionpack/lib/action_controller/abstract/base.rb2
-rw-r--r--actionpack/lib/action_controller/abstract/callbacks.rb2
-rw-r--r--actionpack/lib/action_controller/abstract/helpers.rb6
-rw-r--r--actionpack/lib/action_controller/abstract/layouts.rb5
-rw-r--r--actionpack/lib/action_controller/abstract/logger.rb1
-rw-r--r--actionpack/lib/action_controller/abstract/renderer.rb19
-rw-r--r--actionpack/lib/action_controller/base/compatibility.rb4
-rw-r--r--actionpack/lib/action_controller/base/helpers.rb2
-rw-r--r--actionpack/lib/action_controller/base/http.rb1
-rw-r--r--actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb1
-rw-r--r--actionpack/lib/action_controller/testing/process.rb1
-rw-r--r--actionpack/lib/action_view.rb4
-rw-r--r--actionpack/lib/action_view/base.rb13
-rw-r--r--actionpack/lib/action_view/context.rb44
-rw-r--r--actionpack/lib/action_view/helpers.rb5
-rw-r--r--actionpack/lib/action_view/helpers/active_model_helper.rb (renamed from actionpack/lib/action_view/helpers/active_record_helper.rb)26
-rw-r--r--actionpack/lib/action_view/helpers/ajax_helper.rb68
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb35
-rw-r--r--actionpack/lib/action_view/render/partials.rb6
-rw-r--r--actionpack/lib/action_view/template/renderable.rb8
-rw-r--r--actionpack/lib/action_view/template/template.rb2
-rw-r--r--actionpack/lib/action_view/test_case.rb1
-rw-r--r--actionpack/test/abstract_unit.rb3
-rw-r--r--actionpack/test/activerecord/render_partial_with_record_identification_test.rb1
-rw-r--r--actionpack/test/controller/record_identifier_test.rb1
-rw-r--r--actionpack/test/controller/redirect_test.rb1
-rw-r--r--actionpack/test/javascript/ajax_test.rb115
-rw-r--r--actionpack/test/lib/controller/fake_models.rb2
-rw-r--r--actionpack/test/template/active_record_helper_i18n_test.rb3
-rw-r--r--actionpack/test/template/active_record_helper_test.rb21
-rw-r--r--actionpack/test/template/atom_feed_helper_test.rb10
-rw-r--r--actionpack/test/template/compiled_templates_test.rb2
-rw-r--r--actionpack/test/template/form_helper_test.rb27
-rw-r--r--actionpack/test/template/prototype_helper_test.rb10
-rw-r--r--actionpack/test/template/record_tag_helper_test.rb3
-rw-r--r--actionpack/test/template/url_helper_test.rb2
-rw-r--r--activemodel/examples/validations.rb29
-rw-r--r--activemodel/lib/active_model.rb3
-rw-r--r--activemodel/lib/active_model/attributes.rb25
-rw-r--r--activemodel/lib/active_model/base.rb8
-rw-r--r--activemodel/lib/active_model/conversion.rb8
-rw-r--r--activemodel/lib/active_model/observing.rb97
-rw-r--r--activemodel/lib/active_model/serializers/json.rb1
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb1
-rw-r--r--activemodel/lib/active_model/validations.rb2
-rw-r--r--activemodel/lib/activemodel.rb1
-rw-r--r--activemodel/test/cases/attributes_test.rb30
-rw-r--r--activemodel/test/cases/observing_test.rb7
-rw-r--r--activemodel/test/cases/serializeration/json_serialization_test.rb4
-rw-r--r--activemodel/test/cases/serializeration/xml_serialization_test.rb4
-rw-r--r--activerecord/CHANGELOG4
-rwxr-xr-xactiverecord/lib/active_record/associations.rb9
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb26
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb6
-rw-r--r--activerecord/lib/active_record/autosave_association.rb3
-rwxr-xr-xactiverecord/lib/active_record/base.rb1
-rw-r--r--activerecord/lib/active_record/callbacks.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb98
-rwxr-xr-xactiverecord/test/cases/base_test.rb2
-rw-r--r--activerecord/test/cases/reflection_test.rb10
-rw-r--r--activerecord/test/models/author.rb2
-rw-r--r--activerecord/test/models/company.rb1
-rw-r--r--activerecord/test/models/essay.rb3
-rw-r--r--activerecord/test/models/reply.rb3
-rw-r--r--activerecord/test/models/topic.rb1
-rw-r--r--activerecord/test/schema/schema.rb7
-rw-r--r--activeresource/lib/active_resource.rb1
-rw-r--r--activeresource/lib/active_resource/base.rb13
-rw-r--r--activeresource/lib/active_resource/observing.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/string/interpolation.rb152
-rw-r--r--activesupport/lib/active_support/test_case.rb3
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb13
-rwxr-xr-xrailties/lib/generators/rails/app/templates/script/about.tt2
-rwxr-xr-xrailties/lib/generators/rails/app/templates/script/console.tt2
-rwxr-xr-xrailties/lib/generators/rails/app/templates/script/dbconsole.tt2
-rwxr-xr-xrailties/lib/generators/rails/app/templates/script/destroy.tt2
-rwxr-xr-xrailties/lib/generators/rails/app/templates/script/generate.tt2
-rwxr-xr-xrailties/lib/generators/rails/app/templates/script/performance/benchmarker.tt2
-rwxr-xr-xrailties/lib/generators/rails/app/templates/script/performance/profiler.tt2
-rwxr-xr-xrailties/lib/generators/rails/app/templates/script/plugin.tt2
-rwxr-xr-xrailties/lib/generators/rails/app/templates/script/runner.tt2
-rwxr-xr-xrailties/lib/generators/rails/app/templates/script/server.tt2
-rw-r--r--railties/lib/vendor/bundler/LICENSE20
-rw-r--r--railties/lib/vendor/bundler/Rakefile52
-rwxr-xr-xrailties/lib/vendor/bundler/bin/gem_bundler40
-rw-r--r--railties/lib/vendor/bundler/lib/bundler.rb24
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/cli.rb24
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/dependency.rb35
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/finder.rb42
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/gem_bundle.rb23
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/gem_specification.rb10
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/installer.rb44
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/manifest.rb130
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/resolver.rb19
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/resolver/builders.rb61
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/resolver/engine.rb38
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/resolver/inspect.rb24
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/resolver/search.rb71
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/resolver/stack.rb72
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/resolver/state.rb172
-rw-r--r--railties/lib/vendor/bundler/lib/bundler/runtime.rb39
106 files changed, 1798 insertions, 278 deletions
diff --git a/actionpack/examples/very_simple.rb b/actionpack/examples/very_simple.rb
new file mode 100644
index 0000000000..8d01cb39bd
--- /dev/null
+++ b/actionpack/examples/very_simple.rb
@@ -0,0 +1,50 @@
+$:.push "rails/activesupport/lib"
+$:.push "rails/actionpack/lib"
+
+require "action_controller"
+
+class Kaigi < ActionController::Http
+ include AbstractController::Callbacks
+ include ActionController::RackConvenience
+ include ActionController::Renderer
+ include ActionController::Layouts
+ include ActionView::Context
+
+ before_filter :set_name
+ append_view_path "views"
+
+ def _action_view
+ self
+ end
+
+ def controller
+ self
+ end
+
+ DEFAULT_LAYOUT = Object.new.tap {|l| def l.render(*) yield end }
+
+ def _render_template_from_controller(template, layout = DEFAULT_LAYOUT, options = {}, partial = false)
+ ret = template.render(self, {})
+ layout.render(self, {}) { ret }
+ end
+
+ def index
+ render :template => "template"
+ end
+
+ def alt
+ render :template => "template", :layout => "alt"
+ end
+
+ private
+ def set_name
+ @name = params[:name]
+ end
+end
+
+app = Rack::Builder.new do
+ map("/kaigi") { run Kaigi.action(:index) }
+ map("/kaigi/alt") { run Kaigi.action(:alt) }
+end.to_app
+
+Rack::Handler::Mongrel.run app, :Port => 3000 \ No newline at end of file
diff --git a/actionpack/examples/views/layouts/alt.html.erb b/actionpack/examples/views/layouts/alt.html.erb
new file mode 100644
index 0000000000..c4816337a6
--- /dev/null
+++ b/actionpack/examples/views/layouts/alt.html.erb
@@ -0,0 +1 @@
++ <%= yield %> + \ No newline at end of file
diff --git a/actionpack/examples/views/layouts/kaigi.html.erb b/actionpack/examples/views/layouts/kaigi.html.erb
new file mode 100644
index 0000000000..274607a96a
--- /dev/null
+++ b/actionpack/examples/views/layouts/kaigi.html.erb
@@ -0,0 +1 @@
+Hello <%= yield %> Goodbye \ No newline at end of file
diff --git a/actionpack/examples/views/template.html.erb b/actionpack/examples/views/template.html.erb
new file mode 100644
index 0000000000..3108e9ad70
--- /dev/null
+++ b/actionpack/examples/views/template.html.erb
@@ -0,0 +1 @@
+Hello <%= @name %> \ No newline at end of file
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index e822a11d14..32572c93c0 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -52,11 +52,21 @@ module ActionController
autoload :SessionOverflowError, 'action_controller/base/exceptions'
autoload :UnknownHttpMethod, 'action_controller/base/exceptions'
- require 'action_controller/routing'
+ autoload :Routing, 'action_controller/routing'
end
autoload :HTML, 'action_controller/vendor/html-scanner'
autoload :AbstractController, 'action_controller/abstract'
-require 'action_dispatch'
-require 'action_view'
+autoload :Rack, 'action_dispatch'
+autoload :ActionDispatch, 'action_dispatch'
+autoload :ActionView, 'action_view'
+
+# Common ActiveSupport usage in ActionController
+require "active_support/concern"
+require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/load_error'
+require 'active_support/core_ext/module/attr_internal'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/name_error'
+require 'active_support/inflector' \ No newline at end of file
diff --git a/actionpack/lib/action_controller/abstract/base.rb b/actionpack/lib/action_controller/abstract/base.rb
index a19a236ef7..ca00e66349 100644
--- a/actionpack/lib/action_controller/abstract/base.rb
+++ b/actionpack/lib/action_controller/abstract/base.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/module/attr_internal'
-
module AbstractController
class Base
diff --git a/actionpack/lib/action_controller/abstract/callbacks.rb b/actionpack/lib/action_controller/abstract/callbacks.rb
index 0d5161c80e..ea4b59466e 100644
--- a/actionpack/lib/action_controller/abstract/callbacks.rb
+++ b/actionpack/lib/action_controller/abstract/callbacks.rb
@@ -1,3 +1,5 @@
+require "active_support/new_callbacks"
+
module AbstractController
module Callbacks
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_controller/abstract/helpers.rb b/actionpack/lib/action_controller/abstract/helpers.rb
index 6b73f887c1..5efa37fde3 100644
--- a/actionpack/lib/action_controller/abstract/helpers.rb
+++ b/actionpack/lib/action_controller/abstract/helpers.rb
@@ -8,12 +8,6 @@ module AbstractController
extlib_inheritable_accessor(:_helpers) { Module.new }
end
- # Override AbstractController::Renderer's _action_view to include the
- # helper module for this class into its helpers module.
- def _action_view
- @_action_view ||= super.tap { |av| av.helpers.include(_helpers) }
- end
-
module ClassMethods
# When a class is inherited, wrap its helper module in a new module.
# This ensures that the parent class's module can be changed
diff --git a/actionpack/lib/action_controller/abstract/layouts.rb b/actionpack/lib/action_controller/abstract/layouts.rb
index 2ac4e6068a..f021dd8b62 100644
--- a/actionpack/lib/action_controller/abstract/layouts.rb
+++ b/actionpack/lib/action_controller/abstract/layouts.rb
@@ -6,6 +6,7 @@ module AbstractController
included do
extlib_inheritable_accessor(:_layout_conditions) { Hash.new }
+ _write_layout_method
end
module ClassMethods
@@ -99,7 +100,7 @@ module AbstractController
# the lookup to. By default, layout lookup is limited to the
# formats specified for the current request.
def _layout_for_name(name, details = {:formats => formats})
- name && _find_by_parts(name, details)
+ name && _find_layout(name, details)
end
# Take in the name and details and find a Template.
@@ -111,7 +112,7 @@ module AbstractController
#
# ==== Returns
# Template:: A template object matching the name and details
- def _find_by_parts(name, details)
+ def _find_layout(name, details)
# TODO: Make prefix actually part of details in ViewPath#find_by_parts
prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts"
view_paths.find_by_parts(name, details, prefix)
diff --git a/actionpack/lib/action_controller/abstract/logger.rb b/actionpack/lib/action_controller/abstract/logger.rb
index b960e152e3..fd33bd2ddd 100644
--- a/actionpack/lib/action_controller/abstract/logger.rb
+++ b/actionpack/lib/action_controller/abstract/logger.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/logger'
module AbstractController
diff --git a/actionpack/lib/action_controller/abstract/renderer.rb b/actionpack/lib/action_controller/abstract/renderer.rb
index 611d3a16ce..41b7d47458 100644
--- a/actionpack/lib/action_controller/abstract/renderer.rb
+++ b/actionpack/lib/action_controller/abstract/renderer.rb
@@ -17,25 +17,22 @@ module AbstractController
# An instance of a view class. The default view class is ActionView::Base
#
# The view class must have the following methods:
- # initialize[paths, assigns_for_first_render, controller]
- # paths<Array[ViewPath]>:: A list of resolvers to look for templates in
- # controller<AbstractController::Base> A controller
- # _render_partial_from_controller[options]
+ # View.for_controller[controller] Create a new ActionView instance for a
+ # controller
+ # View#_render_partial_from_controller[options]
+ # - responsible for setting options[:_template]
+ # - Returns String with the rendered partial
# options<Hash>:: see _render_partial in ActionView::Base
- # _render_template_from_controller[template, layout, options, partial]
+ # View#_render_template_from_controller[template, layout, options, partial]
+ # - Returns String with the rendered template
# template<ActionView::Template>:: The template to render
# layout<ActionView::Template>:: The layout to render around the template
# options<Hash>:: See _render_template_with_layout in ActionView::Base
# partial<Boolean>:: Whether or not the template to render is a partial
- # _partial:: If a partial, rather than a template, was rendered, return
- # the partial.
- # helpers:: A module containing the helpers to be used in the view. This
- # module should respond_to include.
- # controller:: The controller that initialized the ActionView
#
# Override this method in a to change the default behavior.
def _action_view
- @_action_view ||= ActionView::Base.new(self.class.view_paths, {}, self)
+ @_action_view ||= ActionView::Base.for_controller(self)
end
# Mostly abstracts the fact that calling render twice is a DoubleRenderError.
diff --git a/actionpack/lib/action_controller/base/compatibility.rb b/actionpack/lib/action_controller/base/compatibility.rb
index cd4b72b1c6..13813ffd17 100644
--- a/actionpack/lib/action_controller/base/compatibility.rb
+++ b/actionpack/lib/action_controller/base/compatibility.rb
@@ -114,7 +114,7 @@ module ActionController
super || (respond_to?(:method_missing) && "_handle_method_missing")
end
- def _find_by_parts(name, details)
+ def _find_layout(name, details)
details[:prefix] = nil if name =~ /\blayouts/
super
end
@@ -123,7 +123,7 @@ module ActionController
def _default_layout(details, require_layout = false)
super
rescue ActionView::MissingTemplate
- _find_by_parts(_layout({}), {})
+ _find_layout(_layout({}), {})
nil
end
diff --git a/actionpack/lib/action_controller/base/helpers.rb b/actionpack/lib/action_controller/base/helpers.rb
index 2fa5ea6519..7c52779064 100644
--- a/actionpack/lib/action_controller/base/helpers.rb
+++ b/actionpack/lib/action_controller/base/helpers.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/load_error'
-require 'active_support/core_ext/name_error'
require 'active_support/dependencies'
module ActionController
diff --git a/actionpack/lib/action_controller/base/http.rb b/actionpack/lib/action_controller/base/http.rb
index ec78bc15a8..3efd1b656f 100644
--- a/actionpack/lib/action_controller/base/http.rb
+++ b/actionpack/lib/action_controller/base/http.rb
@@ -1,5 +1,4 @@
require 'action_controller/abstract'
-require 'active_support/core_ext/module/delegation'
module ActionController
# ActionController::Http provides a way to get a valid Rack application from a controller.
diff --git a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb
index c6f7de17bd..159d869a54 100644
--- a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb
@@ -77,6 +77,7 @@ module ActionController
end
record = extract_record(record_or_hash_or_array)
+ record = record.to_model if record.respond_to?(:to_model)
namespace = extract_namespace(record_or_hash_or_array)
args = case record_or_hash_or_array
diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb
index 7634290ea1..e7c64d0942 100644
--- a/actionpack/lib/action_controller/testing/process.rb
+++ b/actionpack/lib/action_controller/testing/process.rb
@@ -1,3 +1,4 @@
+require 'action_dispatch'
require 'rack/session/abstract/id'
require 'active_support/core_ext/object/conversions'
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 27a06db5bb..70176a0ea4 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -30,12 +30,14 @@ require File.join(File.dirname(__FILE__), "action_pack")
module ActionView
def self.load_all!
- [Base, InlineTemplate, TemplateError]
+ [Context, Base, InlineTemplate, TemplateError]
end
autoload :Base, 'action_view/base'
+ autoload :Context, 'action_view/context'
autoload :Helpers, 'action_view/helpers'
autoload :InlineTemplate, 'action_view/template/inline'
+ autoload :MissingTemplate, 'action_view/base'
autoload :Partials, 'action_view/render/partials'
autoload :Resolver, 'action_view/template/resolver'
autoload :PathSet, 'action_view/paths'
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 45184f58fb..9e696af83b 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -172,8 +172,6 @@ module ActionView #:nodoc:
attr_accessor :controller
attr_internal :captures
- attr_accessor :output_buffer
-
class << self
delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB'
delegate :logger, :to => 'ActionController::Base', :allow_nil => true
@@ -206,10 +204,7 @@ module ActionView #:nodoc:
delegate :find_by_parts, :to => :view_paths
- module CompiledTemplates #:nodoc:
- # holds compiled template code
- end
- include CompiledTemplates
+ include Context
def self.process_view_paths(value)
ActionView::PathSet.new(Array(value))
@@ -228,6 +223,12 @@ module ActionView #:nodoc:
end
end
+ def self.for_controller(controller)
+ new(controller.class.view_paths, {}, controller).tap do |view|
+ view.helpers.include(controller._helpers) if controller.respond_to?(:_helpers)
+ end
+ end
+
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil, formats = nil)#:nodoc:
@formats = formats || [:html]
@assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) }
diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb
new file mode 100644
index 0000000000..f212fe25eb
--- /dev/null
+++ b/actionpack/lib/action_view/context.rb
@@ -0,0 +1,44 @@
+module ActionView
+ module CompiledTemplates #:nodoc:
+ # holds compiled template code
+ end
+
+ # ActionView contexts are supplied to ActionController
+ # to render template. The default ActionView context
+ # is ActionView::Base.
+ #
+ # In order to work with ActionController, a Context
+ # must implement:
+ #
+ # Context.for_controller[controller] Create a new ActionView instance for a
+ # controller
+ # Context#_render_partial_from_controller[options]
+ # - responsible for setting options[:_template]
+ # - Returns String with the rendered partial
+ # options<Hash>:: see _render_partial in ActionView::Base
+ # Context#_render_template_from_controller[template, layout, options, partial]
+ # - Returns String with the rendered template
+ # template<ActionView::Template>:: The template to render
+ # layout<ActionView::Template>:: The layout to render around the template
+ # options<Hash>:: See _render_template_with_layout in ActionView::Base
+ # partial<Boolean>:: Whether or not the template to render is a partial
+ #
+ # An ActionView context can also mix in ActionView's
+ # helpers. In order to mix in helpers, a context must
+ # implement:
+ #
+ # Context#controller
+ # - Returns an instance of AbstractController
+ #
+ # In any case, a context must mix in ActionView::Context,
+ # which stores compiled template and provides the output
+ # buffer.
+ module Context
+ include CompiledTemplates
+ attr_accessor :output_buffer
+
+ def convert_to_model(object)
+ object.respond_to?(:to_model) ? object.to_model : object
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index 97fa2d80e9..652561f7f8 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -1,6 +1,7 @@
module ActionView #:nodoc:
module Helpers #:nodoc:
- autoload :ActiveRecordHelper, 'action_view/helpers/active_record_helper'
+ autoload :ActiveModelHelper, 'action_view/helpers/active_model_helper'
+ autoload :AjaxHelper, 'action_view/helpers/ajax_helper'
autoload :AssetTagHelper, 'action_view/helpers/asset_tag_helper'
autoload :AtomFeedHelper, 'action_view/helpers/atom_feed_helper'
autoload :BenchmarkHelper, 'action_view/helpers/benchmark_helper'
@@ -31,7 +32,7 @@ module ActionView #:nodoc:
include SanitizeHelper::ClassMethods
end
- include ActiveRecordHelper
+ include ActiveModelHelper
include AssetTagHelper
include AtomFeedHelper
include BenchmarkHelper
diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index 75cc651968..0f122d9232 100644
--- a/actionpack/lib/action_view/helpers/active_record_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -15,7 +15,7 @@ module ActionView
# method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This
# is a great way of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form.
# In that case, it's better to use the +input+ method and the specialized +form+ methods in link:classes/ActionView/Helpers/FormHelper.html
- module ActiveRecordHelper
+ module ActiveModelHelper
# Returns a default input tag for the type of object returned by the method. For example, if <tt>@post</tt>
# has an attribute +title+ mapped to a +VARCHAR+ column that holds "Hello World":
#
@@ -77,6 +77,7 @@ module ActionView
# * <tt>:submit_value</tt> - The text of the submit button (default: "Create" if a new record, otherwise "Update").
def form(record_name, options = {})
record = instance_variable_get("@#{record_name}")
+ record = convert_to_model(record)
options = options.symbolize_keys
options[:action] ||= record.new_record? ? "create" : "update"
@@ -121,6 +122,8 @@ module ActionView
end
options.reverse_merge!(:prepend_text => '', :append_text => '', :css_class => 'formError')
+ object = convert_to_model(object)
+
if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
(errors = obj.errors[method])
content_tag("div",
@@ -179,6 +182,8 @@ module ActionView
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
end
+ objects.map! {|o| convert_to_model(o) }
+
count = objects.inject(0) {|sum, object| sum + object.errors.count }
unless count.zero?
html = {}
@@ -226,7 +231,14 @@ module ActionView
end
end
- class InstanceTag #:nodoc:
+ module ActiveRecordInstanceTag
+ def object
+ @active_model_object ||= begin
+ object = super
+ object.respond_to?(:to_model) ? object.to_model : object
+ end
+ end
+
def to_tag(options = {})
case column_type
when :string
@@ -248,11 +260,9 @@ module ActionView
end
%w(tag content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth|
- without = "#{meth}_without_error_wrapping"
- define_method "#{meth}_with_error_wrapping" do |*args|
- error_wrapping(send(without, *args))
+ define_method meth do |*|
+ error_wrapping(super)
end
- alias_method_chain meth, :error_wrapping
end
def error_wrapping(html_tag)
@@ -267,5 +277,9 @@ module ActionView
object.send(:column_for_attribute, @method_name).type
end
end
+
+ class InstanceTag
+ include ActiveRecordInstanceTag
+ end
end
end
diff --git a/actionpack/lib/action_view/helpers/ajax_helper.rb b/actionpack/lib/action_view/helpers/ajax_helper.rb
new file mode 100644
index 0000000000..9cc2acc239
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/ajax_helper.rb
@@ -0,0 +1,68 @@
+module ActionView
+ module Helpers
+ module AjaxHelper
+ include UrlHelper
+
+ def link_to_remote(name, url, options = {})
+ html = options.delete(:html) || {}
+
+ update = options.delete(:update)
+ if update.is_a?(Hash)
+ html["data-update-success"] = update[:success]
+ html["data-update-failure"] = update[:failure]
+ else
+ html["data-update-success"] = update
+ end
+
+ html["data-update-position"] = options.delete(:position)
+ html["data-method"] = options.delete(:method)
+ html["data-remote"] = "true"
+
+ html.merge!(options)
+
+ url = url_for(url) if url.is_a?(Hash)
+ link_to(name, url, html)
+ end
+
+ def button_to_remote(name, options = {}, html_options = {})
+ url = options.delete(:url)
+ url = url_for(url) if url.is_a?(Hash)
+
+ html_options.merge!(:type => "button", :value => name,
+ :"data-url" => url)
+
+ tag(:input, html_options)
+ end
+
+ module Rails2Compatibility
+ def set_callbacks(options, html)
+ [:complete, :failure, :success, :interactive, :loaded, :loading].each do |type|
+ html["data-#{type}-code"] = options.delete(type.to_sym)
+ end
+
+ options.each do |option, value|
+ if option.is_a?(Integer)
+ html["data-#{option}-code"] = options.delete(option)
+ end
+ end
+ end
+
+ def link_to_remote(name, url, options = nil)
+ if !options && url.is_a?(Hash) && url.key?(:url)
+ url, options = url.delete(:url), url
+ end
+
+ set_callbacks(options, options[:html] ||= {})
+
+ super
+ end
+
+ def button_to_remote(name, options = {}, html_options = {})
+ set_callbacks(options, html_options)
+ super
+ end
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 6d6d623938..2d1d19d5f3 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -288,6 +288,8 @@ module ActionView
def apply_form_for_options!(object_or_array, options) #:nodoc:
object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
+ object = convert_to_model(object)
+
html_options =
if object.respond_to?(:new_record?) && object.new_record?
{ :class => dom_class(object, :new), :id => dom_id(object), :method => :post }
@@ -488,7 +490,7 @@ module ActionView
object_name = ActionController::RecordIdentifier.singular_class_name(object)
end
- builder = options[:builder] || ActionView::Base.default_form_builder
+ builder = options[:builder] || ActionView.default_form_builder
yield builder.new(object_name, object, self, options, block)
end
@@ -626,8 +628,8 @@ module ActionView
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
- # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
- # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
+ # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
+ # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
# while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
#
# ==== Gotcha
@@ -709,7 +711,8 @@ module ActionView
end
end
- class InstanceTag #:nodoc:
+ module InstanceTagMethods #:nodoc:
+ extend ActiveSupport::Concern
include Helpers::TagHelper, Helpers::FormTagHelper
attr_reader :method_name, :object_name
@@ -832,7 +835,7 @@ module ActionView
self.class.value_before_type_cast(object, @method_name)
end
- class << self
+ module ClassMethods
def value(object, method_name)
object.send method_name unless object.nil?
end
@@ -918,6 +921,10 @@ module ActionView
end
end
+ class InstanceTag
+ include InstanceTagMethods
+ end
+
class FormBuilder #:nodoc:
# The methods which wrap a form helper call.
class_inheritable_accessor :field_helpers
@@ -1022,7 +1029,7 @@ module ActionView
def fields_for_with_nested_attributes(association_name, args, block)
name = "#{object_name}[#{association_name}_attributes]"
association = @object.send(association_name)
- explicit_object = args.first if args.first.respond_to?(:new_record?)
+ explicit_object = args.first.to_model if args.first.respond_to?(:to_model)
if association.is_a?(Array)
children = explicit_object ? [explicit_object] : association
@@ -1054,9 +1061,21 @@ module ActionView
end
end
- class << Base
+ class << ActionView
attr_accessor :default_form_builder
end
- Base.default_form_builder = ::ActionView::Helpers::FormBuilder
+ self.default_form_builder = ::ActionView::Helpers::FormBuilder
+
+ # 2.3 compatibility
+ class << Base
+ def default_form_builder=(builder)
+ ActionView.default_form_builder = builder
+ end
+
+ def default_form_builder
+ ActionView.default_form_builder
+ end
+ end
+
end
diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb
index a80ffe3c20..ccb14d513a 100644
--- a/actionpack/lib/action_view/render/partials.rb
+++ b/actionpack/lib/action_view/render/partials.rb
@@ -259,7 +259,7 @@ module ActionView
_set_locals(object, locals, template, options)
- self._partial = template
+ options[:_template] = template
_render_template(template, locals)
end
@@ -278,7 +278,7 @@ module ActionView
locals = (options[:locals] ||= {})
index, @_partial_path = 0, nil
collection.map do |object|
- template = passed_template || begin
+ options[:_template] = template = passed_template || begin
_partial_path =
ActionController::RecordIdentifier.partial_path(object, controller_path)
template = _pick_partial_template(_partial_path)
@@ -289,8 +289,6 @@ module ActionView
index += 1
- self._partial = template
-
_render_template(template, locals)
end.join(spacer)
end
diff --git a/actionpack/lib/action_view/template/renderable.rb b/actionpack/lib/action_view/template/renderable.rb
index 54857516ab..7687578165 100644
--- a/actionpack/lib/action_view/template/renderable.rb
+++ b/actionpack/lib/action_view/template/renderable.rb
@@ -12,9 +12,9 @@ module ActionView
end
def load!
- names = Base::CompiledTemplates.instance_methods.grep(/#{method_name_without_locals}/)
+ names = CompiledTemplates.instance_methods.grep(/#{method_name_without_locals}/)
names.each do |name|
- Base::CompiledTemplates.class_eval do
+ CompiledTemplates.class_eval do
remove_method(name)
end
end
@@ -56,7 +56,7 @@ module ActionView
def compile(local_assigns)
render_symbol = method_name(local_assigns)
- if !Base::CompiledTemplates.method_defined?(render_symbol) || recompile?
+ if !CompiledTemplates.method_defined?(render_symbol) || recompile?
compile!(render_symbol, local_assigns)
end
end
@@ -74,7 +74,7 @@ module ActionView
end_src
begin
- ActionView::Base::CompiledTemplates.module_eval(source, filename.to_s, 0)
+ ActionView::CompiledTemplates.module_eval(source, filename.to_s, 0)
rescue Exception => e # errors from template code
if logger = defined?(ActionController) && Base.logger
logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb
index fac50cd692..4145045e2d 100644
--- a/actionpack/lib/action_view/template/template.rb
+++ b/actionpack/lib/action_view/template/template.rb
@@ -75,7 +75,7 @@ module ActionView
end
begin
- ActionView::Base::CompiledTemplates.module_eval(source, identifier, line)
+ ActionView::CompiledTemplates.module_eval(source, identifier, line)
method_name
rescue Exception => e # errors from template code
if logger = (view && view.logger)
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 7355af4192..3f3951509a 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -23,6 +23,7 @@ module ActionView
class TestCase < ActiveSupport::TestCase
include ActionDispatch::Assertions
include ActionController::TestProcess
+ include ActionView::Context
class_inheritable_accessor :helper_class
@@helper_class = nil
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 30e795a7a2..6e71b85645 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -20,6 +20,7 @@ require 'action_controller/testing/process'
require 'action_view/test_case'
require 'action_controller/testing/integration'
require 'active_support/dependencies'
+require 'active_model'
$tags[:new_base] = true
@@ -97,7 +98,7 @@ module ActionController
partials = hax[:partials]
if expected_count = options[:count]
found = partials.detect { |p, _| p.identifier.match(expected_partial) }
- actual_count = found.nil? ? 0 : found.second
+ actual_count = found.nil? ? 0 : found[1]
msg = build_message(message,
"expecting ? to be rendered ? time(s) but rendered ? time(s)",
expected_partial, expected_count, actual_count)
diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
index 2a31f3be44..df50c3dc6f 100644
--- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
+++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
@@ -127,6 +127,7 @@ end
class Game < Struct.new(:name, :id)
extend ActiveModel::Naming
+ include ActiveModel::Conversion
def to_param
id.to_s
end
diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb
index 28bc608d47..44e49ed3f8 100644
--- a/actionpack/test/controller/record_identifier_test.rb
+++ b/actionpack/test/controller/record_identifier_test.rb
@@ -2,6 +2,7 @@ require 'abstract_unit'
class Comment
extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_reader :id
def save; @id = 1 end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 453a77e7bc..b3321303c0 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -5,6 +5,7 @@ end
class Workshop
extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_accessor :id, :new_record
def initialize(id, new_record)
diff --git a/actionpack/test/javascript/ajax_test.rb b/actionpack/test/javascript/ajax_test.rb
new file mode 100644
index 0000000000..b67a91dad3
--- /dev/null
+++ b/actionpack/test/javascript/ajax_test.rb
@@ -0,0 +1,115 @@
+require "abstract_unit"
+
+class AjaxTestCase < ActiveSupport::TestCase
+ include ActionView::Helpers::AjaxHelper
+ include ActionView::Helpers::TagHelper
+
+ def assert_html(html, matches)
+ matches.each do |match|
+ assert_match Regexp.new(Regexp.escape(match)), html
+ end
+ end
+
+ def self.assert_callbacks_work(&blk)
+ define_method(:assert_callbacks_work, &blk)
+
+ [:complete, :failure, :success, :interactive, :loaded, :loading, 404].each do |callback|
+ test "#{callback} callback" do
+ markup = assert_callbacks_work(callback)
+ assert_html markup, %W(data-#{callback}-code="undoRequestCompleted\(request\)")
+ end
+ end
+ end
+end
+
+class LinkToRemoteTest < AjaxTestCase
+ def url_for(hash)
+ "/blog/destroy/4"
+ end
+
+ def link(options = {})
+ link_to_remote("Delete this post", "/blog/destroy/3", options)
+ end
+
+ test "with no update" do
+ assert_html link, %w(href="/blog/destroy/3" Delete\ this\ post data-remote="true")
+ end
+
+ test "basic" do
+ assert_html link(:update => "#posts"),
+ %w(data-update-success="#posts")
+ end
+
+ test "using a url hash" do
+ link = link_to_remote("Delete this post", {:controller => :blog}, :update => "#posts")
+ assert_html link, %w(href="/blog/destroy/4" data-update-success="#posts")
+ end
+
+ test "with :html options" do
+ expected = %{<a href="/blog/destroy/3" data-custom="me" data-update-success="#posts">Delete this post</a>}
+ assert_equal expected, link(:update => "#posts", :html => {"data-custom" => "me"})
+ end
+
+ test "with a hash for :update" do
+ link = link(:update => {:success => "#posts", :failure => "#error"})
+ assert_match /data-update-success="#posts"/, link
+ assert_match /data-update-failure="#error"/, link
+ end
+
+ test "with positional parameters" do
+ link = link(:position => :top, :update => "#posts")
+ assert_match /data\-update\-position="top"/, link
+ end
+
+ test "with an optional method" do
+ link = link(:method => "delete")
+ assert_match /data-method="delete"/, link
+ end
+
+ class LegacyLinkToRemoteTest < AjaxTestCase
+ include ActionView::Helpers::AjaxHelper::Rails2Compatibility
+
+ def link(options)
+ link_to_remote("Delete this post", "/blog/destroy/3", options)
+ end
+
+ test "basic link_to_remote with :url =>" do
+ expected = %{<a href="/blog/destroy/3" data-update-success="#posts">Delete this post</a>}
+ assert_equal expected,
+ link_to_remote("Delete this post", :url => "/blog/destroy/3", :update => "#posts")
+ end
+
+ assert_callbacks_work do |callback|
+ link(callback => "undoRequestCompleted(request)")
+ end
+ end
+end
+
+class ButtonToRemoteTest < AjaxTestCase
+ def button(options, html = {})
+ button_to_remote("Remote outpost", options, html)
+ end
+
+ def url_for(*)
+ "/whatnot"
+ end
+
+ class StandardTest < ButtonToRemoteTest
+ test "basic" do
+ button = button({:url => {:action => "whatnot"}}, {:class => "fine"})
+ [/input/, /class="fine"/, /type="button"/, /value="Remote outpost"/,
+ /data-url="\/whatnot"/].each do |match|
+ assert_match match, button
+ end
+ end
+ end
+
+ class LegacyButtonToRemoteTest < ButtonToRemoteTest
+ include ActionView::Helpers::AjaxHelper::Rails2Compatibility
+
+ assert_callbacks_work do |callback|
+ button(callback => "undoRequestCompleted(request)")
+ end
+ end
+
+end \ No newline at end of file
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index 9e6f14d373..c6726432ec 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -2,6 +2,7 @@ require "active_model"
class Customer < Struct.new(:name, :id)
extend ActiveModel::Naming
+ include ActiveModel::Conversion
def to_param
id.to_s
@@ -17,6 +18,7 @@ end
module Quiz
class Question < Struct.new(:name, :id)
extend ActiveModel::Naming
+ include ActiveModel::Conversion
def to_param
id.to_s
diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb
index 9d04c882c8..63032e4e5c 100644
--- a/actionpack/test/template/active_record_helper_i18n_test.rb
+++ b/actionpack/test/template/active_record_helper_i18n_test.rb
@@ -1,7 +1,8 @@
require 'abstract_unit'
class ActiveRecordHelperI18nTest < Test::Unit::TestCase
- include ActionView::Helpers::ActiveRecordHelper
+ include ActionView::Context
+ include ActionView::Helpers::ActiveModelHelper
attr_reader :request
diff --git a/actionpack/test/template/active_record_helper_test.rb b/actionpack/test/template/active_record_helper_test.rb
index e1be048838..b07ce6cf5d 100644
--- a/actionpack/test/template/active_record_helper_test.rb
+++ b/actionpack/test/template/active_record_helper_test.rb
@@ -1,22 +1,23 @@
require 'abstract_unit'
class ActiveRecordHelperTest < ActionView::TestCase
- tests ActionView::Helpers::ActiveRecordHelper
+ tests ActionView::Helpers::ActiveModelHelper
silence_warnings do
- Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on)
- Post.class_eval do
- alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast)
- alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast)
- alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast)
+ class Post < Struct.new(:title, :author_name, :body, :secret, :written_on)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
end
- User = Struct.new("User", :email)
- User.class_eval do
- alias_method :email_before_type_cast, :email unless respond_to?(:email_before_type_cast)
+ class User < Struct.new(:email)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
end
- Column = Struct.new("Column", :type, :name, :human_name)
+ class Column < Struct.new(:type, :name, :human_name)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ end
end
class DirtyPost
diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb
index 6f1179f359..3acaecd142 100644
--- a/actionpack/test/template/atom_feed_helper_test.rb
+++ b/actionpack/test/template/atom_feed_helper_test.rb
@@ -1,7 +1,13 @@
require 'abstract_unit'
-Scroll = Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at)
-Scroll.extend ActiveModel::Naming
+class Scroll < Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
+ def new_record?
+ true
+ end
+end
class ScrollsController < ActionController::Base
FEEDS = {}
diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb
index 9c268aef27..7734e6da73 100644
--- a/actionpack/test/template/compiled_templates_test.rb
+++ b/actionpack/test/template/compiled_templates_test.rb
@@ -3,7 +3,7 @@ require 'controller/fake_models'
class CompiledTemplatesTest < Test::Unit::TestCase
def setup
- @compiled_templates = ActionView::Base::CompiledTemplates
+ @compiled_templates = ActionView::CompiledTemplates
@compiled_templates.instance_methods.each do |m|
@compiled_templates.send(:remove_method, m) if m =~ /^_render_template_/
end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 515f73c339..99160dd8b1 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -1,11 +1,10 @@
require 'abstract_unit'
silence_warnings do
- Post = Struct.new(:title, :author_name, :body, :secret, :written_on, :cost)
- Post.class_eval do
- alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast)
- alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast)
- alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast)
+ class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
alias_method :secret?, :secret
def new_record=(boolean)
@@ -27,6 +26,9 @@ silence_warnings do
end
class Comment
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
attr_reader :id
attr_reader :post_id
def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end
@@ -43,6 +45,9 @@ silence_warnings do
end
class Tag
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
attr_reader :id
attr_reader :post_id
def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end
@@ -59,6 +64,9 @@ silence_warnings do
end
class CommentRelevance
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
attr_reader :id
attr_reader :comment_id
def initialize(id = nil, comment_id = nil); @id, @comment_id = id, comment_id end
@@ -71,6 +79,9 @@ silence_warnings do
end
class TagRelevance
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
attr_reader :id
attr_reader :tag_id
def initialize(id = nil, tag_id = nil); @id, @tag_id = id, tag_id end
@@ -1024,8 +1035,8 @@ class FormHelperTest < ActionView::TestCase
end
def test_default_form_builder
- old_default_form_builder, ActionView::Base.default_form_builder =
- ActionView::Base.default_form_builder, LabelledFormBuilder
+ old_default_form_builder, ActionView.default_form_builder =
+ ActionView.default_form_builder, LabelledFormBuilder
form_for(:post, @post) do |f|
concat f.text_field(:title)
@@ -1042,7 +1053,7 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
ensure
- ActionView::Base.default_form_builder = old_default_form_builder
+ ActionView.default_form_builder = old_default_form_builder
end
def test_default_form_builder_with_active_record_helpers
diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb
index a7a1bc99f3..acbf311212 100644
--- a/actionpack/test/template/prototype_helper_test.rb
+++ b/actionpack/test/template/prototype_helper_test.rb
@@ -1,10 +1,15 @@
require 'abstract_unit'
+require 'active_model'
-Bunny = Struct.new(:Bunny, :id)
-Bunny.extend ActiveModel::Naming
+class Bunny < Struct.new(:Bunny, :id)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+end
class Author
extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
attr_reader :id
def save; @id = 1 end
def new_record?; @id.nil? end
@@ -15,6 +20,7 @@ end
class Article
extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_reader :id
attr_reader :author_id
def save; @id = 1; @author_id = 1 end
diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb
index 5b840d123b..4144fea678 100644
--- a/actionpack/test/template/record_tag_helper_test.rb
+++ b/actionpack/test/template/record_tag_helper_test.rb
@@ -2,11 +2,12 @@ require 'abstract_unit'
class Post
extend ActiveModel::Naming
+ include ActiveModel::Conversion
def id
45
end
def body
- "What a wonderful world!"
+ super || "What a wonderful world!"
end
end
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index f0364fd660..9eeb26831c 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -495,6 +495,7 @@ end
class Workshop
extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_accessor :id, :new_record
def initialize(id, new_record)
@@ -512,6 +513,7 @@ end
class Session
extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_accessor :id, :workshop_id, :new_record
def initialize(id, new_record)
diff --git a/activemodel/examples/validations.rb b/activemodel/examples/validations.rb
new file mode 100644
index 0000000000..3f8311ff96
--- /dev/null
+++ b/activemodel/examples/validations.rb
@@ -0,0 +1,29 @@
+require 'activemodel'
+
+class Person
+ include ActiveModel::Conversion
+ include ActiveModel::Validations
+
+ validates_presence_of :name
+
+ attr_accessor :name
+
+ def initialize(attributes = {})
+ @name = attributes[:name]
+ end
+
+ def persist
+ @persisted = true
+ end
+
+ def new_record?
+ @persisted
+ end
+end
+
+person1 = Person.new
+p person1.valid?
+person1.errors
+
+person2 = Person.new(:name => "matz")
+p person2.valid?
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index f988cd71b8..2de19597b1 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -26,8 +26,7 @@ $:.unshift(activesupport_path) if File.directory?(activesupport_path)
require 'active_support'
module ActiveModel
- autoload :Attributes, 'active_model/attributes'
- autoload :Base, 'active_model/base'
+ autoload :Conversion, 'active_model/conversion'
autoload :DeprecatedErrorMethods, 'active_model/deprecated_error_methods'
autoload :Errors, 'active_model/errors'
autoload :Name, 'active_model/naming'
diff --git a/activemodel/lib/active_model/attributes.rb b/activemodel/lib/active_model/attributes.rb
deleted file mode 100644
index ea8c8d5f72..0000000000
--- a/activemodel/lib/active_model/attributes.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'active_support/core_ext/object/instance_variables'
-
-module ActiveModel
- module Attributes
- def self.append_features(base)
- unless base.instance_methods.include?('attributes')
- super
- else
- false
- end
- end
-
- def attributes
- instance_values
- end
-
- def read_attribute(attr_name)
- instance_variable_get(:"@#{attr_name}")
- end
-
- def write_attribute(attr_name, value)
- instance_variable_set(:"@#{attr_name}", value)
- end
- end
-end
diff --git a/activemodel/lib/active_model/base.rb b/activemodel/lib/active_model/base.rb
deleted file mode 100644
index a500adfdf1..0000000000
--- a/activemodel/lib/active_model/base.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-module ActiveModel
- class Base
- include Observing
- # disabled, until they're tested
- # include Callbacks
- # include Validations
- end
-end \ No newline at end of file
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
new file mode 100644
index 0000000000..d5c65920f6
--- /dev/null
+++ b/activemodel/lib/active_model/conversion.rb
@@ -0,0 +1,8 @@
+module ActiveModel
+ # Include ActiveModel::Conversion if your object "acts like an ActiveModel model".
+ module Conversion
+ def to_model
+ self
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index 7bad2397ae..3b230c43b9 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -1,7 +1,8 @@
require 'observer'
require 'singleton'
-require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/array/wrap'
+require 'active_support/core_ext/module/aliasing'
+require 'active_support/core_ext/string/inflections'
module ActiveModel
module Observing
@@ -39,8 +40,25 @@ module ActiveModel
observers.each { |o| instantiate_observer(o) }
end
+ # Wraps methods with before and after notifications.
+ #
+ # wrap_with_notifications :create, :save, :update, :destroy
+ def wrap_with_notifications(*methods)
+ methods.each do |method|
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{method}_with_notifications(*args, &block)
+ notify_observers(:before_#{method})
+ result = #{method}_without_notifications(*args, &block)
+ notify_observers(:after_#{method})
+ result
+ end
+ EOS
+ alias_method_chain(method, :notifications)
+ end
+ end
+
protected
- def instantiate_observer(observer)
+ def instantiate_observer(observer) #:nodoc:
# string/symbol
if observer.respond_to?(:to_sym)
observer = observer.to_s.camelize.constantize.instance
@@ -60,12 +78,72 @@ module ActiveModel
end
private
- def notify(method) #:nodoc:
+ # Fires notifications to model's observers
+ #
+ # def save
+ # notify_observers(:before_save)
+ # ...
+ # notify_observers(:after_save)
+ # end
+ def notify_observers(method)
self.class.changed
self.class.notify_observers(method, self)
end
end
+ # Observer classes respond to lifecycle callbacks to implement trigger-like
+ # behavior outside the original class. This is a great way to reduce the
+ # clutter that normally comes when the model class is burdened with
+ # functionality that doesn't pertain to the core responsibility of the
+ # class. Example:
+ #
+ # class CommentObserver < ActiveModel::Observer
+ # def after_save(comment)
+ # Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
+ # end
+ # end
+ #
+ # This Observer sends an email when a Comment#save is finished.
+ #
+ # class ContactObserver < ActiveModel::Observer
+ # def after_create(contact)
+ # contact.logger.info('New contact added!')
+ # end
+ #
+ # def after_destroy(contact)
+ # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
+ # end
+ # end
+ #
+ # This Observer uses logger to log when specific callbacks are triggered.
+ #
+ # == 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):
+ #
+ # class AuditObserver < ActiveModel::Observer
+ # observe :account
+ #
+ # def after_update(account)
+ # AuditTrail.new(account, "UPDATED")
+ # end
+ # end
+ #
+ # 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
+ #
+ # def after_update(record)
+ # AuditTrail.new(record, "UPDATED")
+ # end
+ # end
+ #
+ # The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
+ #
class Observer
include Singleton
@@ -77,6 +155,15 @@ module ActiveModel
define_method(:observed_classes) { models }
end
+ # Returns an array of Classes to observe.
+ #
+ # You can override this instead of using the +observe+ helper.
+ #
+ # class AuditObserver < ActiveModel::Observer
+ # def self.observed_classes
+ # [AccountObserver, BalanceObserver]
+ # end
+ # end
def observed_classes
Array.wrap(observed_class)
end
@@ -97,7 +184,7 @@ module ActiveModel
observed_classes.each { |klass| add_observer!(klass) }
end
- def observed_classes
+ def observed_classes #:nodoc:
self.class.observed_classes
end
@@ -114,7 +201,7 @@ module ActiveModel
end
protected
- def add_observer!(klass)
+ def add_observer!(klass) #:nodoc:
klass.add_observer(self)
end
end
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index adf200597d..e94512fd64 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -5,7 +5,6 @@ module ActiveModel
module Serializers
module JSON
extend ActiveSupport::Concern
- include ActiveModel::Attributes
included do
extend ActiveModel::Naming
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 7cdd281223..76a0e54a56 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -5,7 +5,6 @@ module ActiveModel
module Serializers
module Xml
extend ActiveSupport::Concern
- include ActiveModel::Attributes
class Serializer < ActiveModel::Serializer #:nodoc:
class Attribute #:nodoc:
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 5223cea135..54a869396d 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -1,5 +1,7 @@
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/keys'
+require 'active_support/concern'
+require 'active_support/callbacks'
module ActiveModel
module Validations
diff --git a/activemodel/lib/activemodel.rb b/activemodel/lib/activemodel.rb
new file mode 100644
index 0000000000..da3133103b
--- /dev/null
+++ b/activemodel/lib/activemodel.rb
@@ -0,0 +1 @@
+require 'active_model'
diff --git a/activemodel/test/cases/attributes_test.rb b/activemodel/test/cases/attributes_test.rb
deleted file mode 100644
index 5f3ea839a4..0000000000
--- a/activemodel/test/cases/attributes_test.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'cases/helper'
-
-class AttributesTest < ActiveModel::TestCase
- class Person
- include ActiveModel::Attributes
- attr_accessor :name
- end
-
- test "reads attribute" do
- p = Person.new
- assert_equal nil, p.read_attribute(:name)
-
- p.name = "Josh"
- assert_equal "Josh", p.read_attribute(:name)
- end
-
- test "writes attribute" do
- p = Person.new
- assert_equal nil, p.name
-
- p.write_attribute(:name, "Josh")
- assert_equal "Josh", p.name
- end
-
- test "returns all attributes" do
- p = Person.new
- p.name = "Josh"
- assert_equal({"name" => "Josh"}, p.attributes)
- end
-end
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
index 564451fa2f..fbf93c19ef 100644
--- a/activemodel/test/cases/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -1,6 +1,8 @@
require 'cases/helper'
-class ObservedModel < ActiveModel::Base
+class ObservedModel
+ include ActiveModel::Observing
+
class Observer
end
end
@@ -17,7 +19,8 @@ class FooObserver < ActiveModel::Observer
end
end
-class Foo < ActiveModel::Base
+class Foo
+ include ActiveModel::Observing
end
class ObservingTest < ActiveModel::TestCase
diff --git a/activemodel/test/cases/serializeration/json_serialization_test.rb b/activemodel/test/cases/serializeration/json_serialization_test.rb
index 3e69db110e..6227aedc39 100644
--- a/activemodel/test/cases/serializeration/json_serialization_test.rb
+++ b/activemodel/test/cases/serializeration/json_serialization_test.rb
@@ -3,6 +3,10 @@ require 'models/contact'
class Contact
include ActiveModel::Serializers::JSON
+
+ def attributes
+ instance_values
+ end
end
class JsonSerializationTest < ActiveModel::TestCase
diff --git a/activemodel/test/cases/serializeration/xml_serialization_test.rb b/activemodel/test/cases/serializeration/xml_serialization_test.rb
index 57792e900e..e459f6433a 100644
--- a/activemodel/test/cases/serializeration/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializeration/xml_serialization_test.rb
@@ -3,6 +3,10 @@ require 'models/contact'
class Contact
include ActiveModel::Serializers::Xml
+
+ def attributes
+ instance_values
+ end
end
class XmlSerializationTest < ActiveModel::TestCase
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 411b640c9e..659de99873 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,9 @@
*Edge*
+* Added :primary_key option to belongs_to associations. #765 [Szymon Nowak, Philip Hallstrom, Noel Rocha]
+ # employees.company_name references companies.name
+ Employee.belongs_to :company, :primary_key => 'name', :foreign_key => 'company_name'
+
* Implement #many? for NamedScope and AssociationCollection using #size. #1500 [Chris Kampmeier]
* Added :touch option to belongs_to associations that will touch the parent record when the current record is saved or destroyed [DHH]
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 419967b833..934beb7d39 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -963,6 +963,8 @@ module ActiveRecord
# of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> association will use
# "person_id" as the default <tt>:foreign_key</tt>. Similarly, <tt>belongs_to :favorite_person, :class_name => "Person"</tt>
# will use a foreign key of "favorite_person_id".
+ # [:primary_key]
+ # Specify the method that returns the primary key of associated object used for the association. By default this is id.
# [:dependent]
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. This option should not be specified when
@@ -993,6 +995,7 @@ module ActiveRecord
#
# Option examples:
# belongs_to :firm, :foreign_key => "client_of"
+ # belongs_to :person, :primary_key => "name", :foreign_key => "person_name"
# belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
# belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
# :conditions => 'discounts > #{payments_count}'
@@ -1328,14 +1331,14 @@ module ActiveRecord
method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
define_method(method_name) do
association = send(reflection.name)
- association.class.increment_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
+ association.class.increment_counter(cache_column, association.id) unless association.nil?
end
after_create(method_name)
method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
association = send(reflection.name)
- association.class.decrement_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
+ association.class.decrement_counter(cache_column, association.id) unless association.nil?
end
before_destroy(method_name)
@@ -1527,7 +1530,7 @@ module ActiveRecord
mattr_accessor :valid_keys_for_belongs_to_association
@@valid_keys_for_belongs_to_association = [
- :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions,
+ :class_name, :primary_key, :foreign_key, :foreign_type, :remote, :select, :conditions,
:include, :dependent, :counter_cache, :extend, :polymorphic, :readonly,
:validate, :touch, :inverse_of
]
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index c88575048a..628033c87a 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -14,7 +14,7 @@ module ActiveRecord
if record.nil?
if counter_cache_name && !@owner.new_record?
- @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
+ @reflection.klass.decrement_counter(counter_cache_name, previous_record_id) if @owner[@reflection.primary_key_name]
end
@target = @owner[@reflection.primary_key_name] = nil
@@ -27,7 +27,7 @@ module ActiveRecord
end
@target = (AssociationProxy === record ? record.target : record)
- @owner[@reflection.primary_key_name] = record.id unless record.new_record?
+ @owner[@reflection.primary_key_name] = record_id(record) unless record.new_record?
@updated = true
end
@@ -43,13 +43,18 @@ module ActiveRecord
private
def find_target
- the_target = @reflection.klass.find(
+ find_method = if @reflection.options[:primary_key]
+ "find_by_#{@reflection.options[:primary_key]}"
+ else
+ "find"
+ end
+ the_target = @reflection.klass.send(find_method,
@owner[@reflection.primary_key_name],
:select => @reflection.options[:select],
:conditions => conditions,
:include => @reflection.options[:include],
:readonly => @reflection.options[:readonly]
- )
+ ) if @owner[@reflection.primary_key_name]
set_inverse_instance(the_target, @owner)
the_target
end
@@ -63,6 +68,19 @@ module ActiveRecord
def we_can_set_the_inverse_on_this?(record)
@reflection.has_inverse? && @reflection.inverse_of.macro == :has_one
end
+
+ def record_id(record)
+ record.send(@reflection.options[:primary_key] || :id)
+ end
+
+ def previous_record_id
+ @previous_record_id ||= if @reflection.options[:primary_key]
+ previous_record = @owner.send(@reflection.name)
+ previous_record.nil? ? nil : previous_record.id
+ else
+ @owner[@reflection.primary_key_name]
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index d8146daa54..67e18d692d 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -7,7 +7,7 @@ module ActiveRecord
else
@target = (AssociationProxy === record ? record.target : record)
- @owner[@reflection.primary_key_name] = record.id
+ @owner[@reflection.primary_key_name] = record_id(record)
@owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
@updated = true
@@ -41,6 +41,10 @@ module ActiveRecord
!@owner[@reflection.primary_key_name].nil?
end
+ def record_id(record)
+ record.send(@reflection.options[:primary_key] || :id)
+ end
+
def association_class
@owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index a540570f42..c1bc8423a9 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -339,7 +339,8 @@ module ActiveRecord
association.save(!autosave) if association.new_record? || autosave
if association.updated?
- self[reflection.primary_key_name] = association.id
+ association_id = association.send(reflection.options[:primary_key] || :id)
+ 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
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index deab56e219..62e97158ec 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -3179,6 +3179,7 @@ module ActiveRecord #:nodoc:
include Dirty
include Callbacks, ActiveModel::Observing, Timestamp
include Associations, AssociationPreload, NamedScope
+ include ActiveModel::Conversion
# AutosaveAssociation needs to be included before Transactions, because we want
# #save_with_autosave_associations to be wrapped inside a transaction.
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 01e41c04df..3aa0b8f1b5 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -349,7 +349,7 @@ module ActiveRecord
result = send(method)
end
- notify(method)
+ notify_observers(method)
return result
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 13a78a1890..ab6f752243 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -14,6 +14,7 @@ require 'models/tagging'
require 'models/comment'
require 'models/sponsor'
require 'models/member'
+require 'models/essay'
class BelongsToAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :topics,
@@ -25,6 +26,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert !Client.find(3).firm.nil?, "Microsoft should have a firm"
end
+ def test_belongs_to_with_primary_key
+ client = Client.create(:name => "Primary key client", :firm_name => companies(:first_firm).name)
+ assert_equal companies(:first_firm).name, client.firm_with_primary_key.name
+ end
+
def test_proxy_assignment
account = Account.find(1)
assert_nothing_raised { account.firm = account.firm }
@@ -47,6 +53,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal apple.id, citibank.firm_id
end
+ def test_natural_assignment_with_primary_key
+ apple = Firm.create("name" => "Apple")
+ citibank = Client.create("name" => "Primary key client")
+ citibank.firm_with_primary_key = apple
+ assert_equal apple.name, citibank.firm_name
+ end
+
def test_no_unexpected_aliasing
first_firm = companies(:first_firm)
another_firm = companies(:another_firm)
@@ -69,6 +82,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal apple, citibank.firm
end
+ def test_creating_the_belonging_object_with_primary_key
+ client = Client.create(:name => "Primary key client")
+ apple = client.create_firm_with_primary_key("name" => "Apple")
+ assert_equal apple, client.firm_with_primary_key
+ client.save
+ client.reload
+ assert_equal apple, client.firm_with_primary_key
+ end
+
def test_building_the_belonging_object
citibank = Account.create("credit_limit" => 10)
apple = citibank.build_firm("name" => "Apple")
@@ -76,6 +98,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal apple.id, citibank.firm_id
end
+ def test_building_the_belonging_object_with_primary_key
+ client = Client.create(:name => "Primary key client")
+ apple = client.build_firm_with_primary_key("name" => "Apple")
+ client.save
+ assert_equal apple.name, client.firm_name
+ end
+
def test_natural_assignment_to_nil
client = Client.find(3)
client.firm = nil
@@ -84,6 +113,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_nil client.client_of
end
+ def test_natural_assignment_to_nil_with_primary_key
+ client = Client.create(:name => "Primary key client", :firm_name => companies(:first_firm).name)
+ client.firm_with_primary_key = nil
+ client.save
+ assert_nil client.firm_with_primary_key(true)
+ assert_nil client.client_of
+ end
+
def test_with_different_class_name
assert_equal Company.find(1).name, Company.find(3).firm_with_other_name.name
assert_not_nil Company.find(3).firm_with_other_name, "Microsoft should have a firm"
@@ -110,6 +147,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted"
end
+ def test_belongs_to_with_primary_key_counter
+ debate = Topic.create("title" => "debate")
+ assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet"
+
+ trash = debate.replies_with_primary_key.create("title" => "blah!", "content" => "world around!")
+ assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created"
+
+ trash.destroy
+ assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted"
+ end
+
def test_belongs_to_counter_with_assigning_nil
p = Post.find(1)
c = Comment.find(1)
@@ -122,6 +170,18 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Post.find(p.id).comments.size
end
+ def test_belongs_to_with_primary_key_counter_with_assigning_nil
+ debate = Topic.create("title" => "debate")
+ reply = Reply.create("title" => "blah!", "content" => "world around!", "parent_title" => "debate")
+
+ assert_equal debate.title, reply.parent_title
+ assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count")
+
+ reply.topic_with_primary_key = nil
+
+ assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count")
+ end
+
def test_belongs_to_counter_with_reassigning
t1 = Topic.create("title" => "t1")
t2 = Topic.create("title" => "t2")
@@ -219,6 +279,18 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal firm, final_cut.firm(true)
end
+ def test_assignment_before_child_saved_with_primary_key
+ final_cut = Client.new("name" => "Final Cut")
+ firm = Firm.find(1)
+ final_cut.firm_with_primary_key = firm
+ assert final_cut.new_record?
+ assert final_cut.save
+ assert !final_cut.new_record?
+ assert !firm.new_record?
+ assert_equal firm, final_cut.firm_with_primary_key
+ assert_equal firm, final_cut.firm_with_primary_key(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
@@ -304,6 +376,20 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
sponsor.sponsorable = member
assert_equal "Member", sponsor.sponsorable_type
end
+
+ def test_polymorphic_assignment_with_primary_key_foreign_type_field_updating
+ # should update when assigning a saved record
+ essay = Essay.new
+ writer = Author.create(:name => "David")
+ essay.writer = writer
+ assert_equal "Author", essay.writer_type
+
+ # should update when assigning a new record
+ essay = Essay.new
+ writer = Author.new
+ essay.writer = writer
+ assert_equal "Author", essay.writer_type
+ end
def test_polymorphic_assignment_updates_foreign_id_field_for_new_and_saved_records
sponsor = Sponsor.new
@@ -317,6 +403,18 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal nil, sponsor.sponsorable_id
end
+ def test_polymorphic_assignment_with_primary_key_updates_foreign_id_field_for_new_and_saved_records
+ essay = Essay.new
+ saved_writer = Author.create(:name => "David")
+ new_writer = Author.new
+
+ essay.writer = saved_writer
+ assert_equal saved_writer.name, essay.writer_id
+
+ essay.writer = new_writer
+ assert_equal nil, essay.writer_id
+ end
+
def test_belongs_to_proxy_should_not_respond_to_private_methods
assert_raise(NoMethodError) { companies(:first_firm).private_method }
assert_raise(NoMethodError) { companies(:second_client).firm.private_method }
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index f9ac37cc87..e47f898485 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -2026,7 +2026,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_inspect_instance
topic = topics(:first)
- assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, type: nil>), topic.inspect
+ assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil>), topic.inspect
end
def test_inspect_new_instance
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 194d5e9dff..4083b990d9 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -27,25 +27,25 @@ class ReflectionTest < ActiveRecord::TestCase
def test_read_attribute_names
assert_equal(
- %w( id title author_name author_email_address bonus_time written_on last_read content approved replies_count parent_id type ).sort,
+ %w( id title author_name author_email_address bonus_time written_on last_read content approved replies_count parent_id parent_title type ).sort,
@first.attribute_names
)
end
def test_columns
- assert_equal 12, Topic.columns.length
+ assert_equal 13, Topic.columns.length
end
def test_columns_are_returned_in_the_order_they_were_declared
column_names = Topic.columns.map { |column| column.name }
- assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id type), column_names
+ assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type), column_names
end
def test_content_columns
content_columns = Topic.content_columns
content_column_names = content_columns.map {|column| column.name}
- assert_equal 8, content_columns.length
- assert_equal %w(title author_name author_email_address written_on bonus_time last_read content approved).sort, content_column_names.sort
+ assert_equal 9, content_columns.length
+ assert_equal %w(title author_name author_email_address written_on bonus_time last_read content approved parent_title).sort, content_column_names.sort
end
def test_column_string_type_and_limit
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 0d9ee36b20..b844c7cce0 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -87,6 +87,8 @@ class Author < ActiveRecord::Base
has_many :tags, :through => :posts # through has_many :through
has_many :post_categories, :through => :posts, :source => :categories
+ has_one :essay, :primary_key => :name, :as => :writer
+
belongs_to :author_address, :dependent => :destroy
belongs_to :author_address_extra, :dependent => :delete, :class_name => "AuthorAddress"
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 840527ddeb..22168468a6 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -88,6 +88,7 @@ class Client < Company
belongs_to :firm_with_select, :class_name => "Firm", :foreign_key => "firm_id", :select => "id"
belongs_to :firm_with_other_name, :class_name => "Firm", :foreign_key => "client_of"
belongs_to :firm_with_condition, :class_name => "Firm", :foreign_key => "client_of", :conditions => ["1 = ?", 1]
+ belongs_to :firm_with_primary_key, :class_name => "Firm", :primary_key => "name", :foreign_key => "firm_name"
belongs_to :readonly_firm, :class_name => "Firm", :foreign_key => "firm_id", :readonly => true
# Record destruction so we can test whether firm.clients.clear has
diff --git a/activerecord/test/models/essay.rb b/activerecord/test/models/essay.rb
new file mode 100644
index 0000000000..6c28f5e49b
--- /dev/null
+++ b/activerecord/test/models/essay.rb
@@ -0,0 +1,3 @@
+class Essay < ActiveRecord::Base
+ belongs_to :writer, :primary_key => :name, :polymorphic => true
+end
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index 616c07687c..f5906dedd1 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -4,12 +4,13 @@ class Reply < Topic
named_scope :base
belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true
+ belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count"
has_many :replies, :class_name => "SillyReply", :dependent => :destroy, :foreign_key => "parent_id"
validate :errors_on_empty_content
validate_on_create :title_is_wrong_create
- attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read
+ attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read, :parent_title
validate :check_empty_title
validate_on_create :check_content_mismatch
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 51012d22ed..201d96dcd7 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -39,6 +39,7 @@ class Topic < ActiveRecord::Base
named_scope :by_rejected_ids, lambda {{ :conditions => { :id => all(:conditions => {:approved => false}).map(&:id) } }}
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
+ has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title"
serialize :content
before_create :default_written_on
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index d2d6d1f4b3..2b7d3856b7 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -161,6 +161,12 @@ ActiveRecord::Schema.define do
t.integer :course_id, :null => false
end
+ create_table :essays, :force => true do |t|
+ t.string :name
+ t.string :writer_id
+ t.string :writer_type
+ end
+
create_table :events, :force => true do |t|
t.string :title, :limit => 5
end
@@ -421,6 +427,7 @@ ActiveRecord::Schema.define do
t.boolean :approved, :default => true
t.integer :replies_count, :default => 0
t.integer :parent_id
+ t.string :parent_title
t.string :type
end
diff --git a/activeresource/lib/active_resource.rb b/activeresource/lib/active_resource.rb
index 1dcb795a7d..fd4c199b48 100644
--- a/activeresource/lib/active_resource.rb
+++ b/activeresource/lib/active_resource.rb
@@ -34,6 +34,7 @@ module ActiveResource
autoload :Connection, 'active_resource/connection'
autoload :CustomMethods, 'active_resource/custom_methods'
autoload :Formats, 'active_resource/formats'
+ autoload :Observing, 'active_resource/observing'
autoload :Validations, 'active_resource/validations'
autoload :HttpMock, 'active_resource/http_mock'
end
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index 88a431a6d9..bc82139dac 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -804,8 +804,7 @@ module ActiveResource
# my_company.size = 10
# my_company.save # sends PUT /companies/1 (update)
def save
- notify(:before_save)
- (new? ? create : update).tap { notify(:after_save) }
+ new? ? create : update
end
# Deletes the resource from the remote service.
@@ -821,8 +820,7 @@ module ActiveResource
# new_person.destroy
# Person.find(new_id) # 404 (Resource Not Found)
def destroy
- notify(:before_destroy)
- connection.delete(element_path, self.class.headers).tap { notify(:after_destroy) }
+ connection.delete(element_path, self.class.headers)
end
# Evaluates to <tt>true</tt> if this resource is not <tt>new?</tt> and is
@@ -997,20 +995,16 @@ module ActiveResource
# Update the resource on the remote service.
def update
- notify(:before_update)
connection.put(element_path(prefix_options), encode, self.class.headers).tap do |response|
load_attributes_from_response(response)
- notify(:after_update)
end
end
# Create (i.e., \save to the remote service) the \new resource.
def create
- notify(:before_create)
connection.post(collection_path, encode, self.class.headers).tap do |response|
self.id = id_from_response(response)
load_attributes_from_response(response)
- notify(:after_create)
end
end
@@ -1093,7 +1087,6 @@ module ActiveResource
class Base
extend ActiveModel::Naming
- include CustomMethods, Validations
- include ActiveModel::Observing
+ include CustomMethods, Observing, Validations
end
end
diff --git a/activeresource/lib/active_resource/observing.rb b/activeresource/lib/active_resource/observing.rb
new file mode 100644
index 0000000000..94836f4bb1
--- /dev/null
+++ b/activeresource/lib/active_resource/observing.rb
@@ -0,0 +1,10 @@
+module ActiveResource
+ module Observing
+ extend ActiveSupport::Concern
+ include ActiveModel::Observing
+
+ included do
+ wrap_with_notifications :create, :save, :update, :destroy
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/string/interpolation.rb b/activesupport/lib/active_support/core_ext/string/interpolation.rb
index b21977ecc1..d459c03d39 100644
--- a/activesupport/lib/active_support/core_ext/string/interpolation.rb
+++ b/activesupport/lib/active_support/core_ext/string/interpolation.rb
@@ -1,87 +1,93 @@
-if RUBY_VERSION < '1.9'
-
=begin
- string.rb - Extension for String.
-
+ 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
-
- You may redistribute it and/or modify it under the same
- license terms as Ruby.
+ You may redistribute it and/or modify it under the same license terms as Ruby.
=end
-# This feature is included in Ruby 1.9 or later but not occur TypeError.
-#
-# String#% method which accepts named arguments. Particularly useful if the
-# string is to be used by a translator because named arguments mean more
-# than %s/%d style.
-class String
+if RUBY_VERSION < '1.9'
- unless instance_methods.find {|m| m.to_s == 'bytesize'}
- # For older ruby (such as ruby-1.8.5)
- alias :bytesize :size
- end
+ # KeyError is raised by String#% when the string contains a named placeholder
+ # that is not contained in the given arguments hash. Ruby 1.9 includes and
+ # raises this exception natively. We define it to mimic Ruby 1.9's behaviour
+ # in Ruby 1.8.x
- alias :_old_format_m :% # :nodoc:
+ class KeyError < IndexError
+ def initialize(message = nil)
+ super(message || "key not found")
+ end
+ end unless defined?(KeyError)
- PERCENT_MATCH_RE = Regexp.union(
+ # Extension for String class. This feature is included in Ruby 1.9 or later but not occur TypeError.
+ #
+ # String#% method which accept "named argument". The translator can know
+ # the meaning of the msgids using "named argument" instead of %s/%d style.
+
+ class String
+ # For older ruby versions, such as ruby-1.8.5
+ alias :bytesize :size unless instance_methods.find {|m| m.to_s == 'bytesize'}
+ alias :interpolate_without_ruby_19_syntax :% # :nodoc:
+
+ INTERPOLATION_PATTERN = Regexp.union(
/%%/,
- /%\{(\w+)\}/,
- /%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/
- )
+ /%\{(\w+)\}/, # matches placeholders like "%{foo}"
+ /%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
+ )
- # call-seq:
- # %(arg)
- # %(hash)
- #
- # Format - Uses str as a format specification, and returns the result of applying it to arg.
- # If the format specification contains more than one substitution, then arg must be
- # an Array containing the values to be substituted. See Kernel::sprintf for details of the
- # format string. This is the default behavior of the String class.
- # * arg: an Array or other class except Hash.
- # * Returns: formatted String
- # Example:
- # "%s, %s" % ["Masao", "Mutoh"]
- #
- # Also you can use a Hash as the "named argument". This is recommended way so translators
- # can understand the meanings of the msgids easily.
- # * hash: {:key1 => value1, :key2 => value2, ... }
- # * Returns: formatted String
- # Example:
- # For strings.
- # "%{firstname}, %{familyname}" % {:firstname => "Masao", :familyname => "Mutoh"}
- #
- # With field type to specify format such as d(decimal), f(float),...
- # "%<age>d, %<weight>.1f" % {:age => 10, :weight => 43.4}
- def %(args)
- if args.kind_of?(Hash)
- ret = dup
- ret.gsub!(PERCENT_MATCH_RE) {|match|
- if match == '%%'
- '%'
- elsif $1
- key = $1.to_sym
- args.has_key?(key) ? args[key] : match
- elsif $2
- key = $2.to_sym
- args.has_key?(key) ? sprintf("%#{$3}", args[key]) : match
- end
- }
- ret
- else
- ret = gsub(/%([{<])/, '%%\1')
- begin
- ret._old_format_m(args)
- rescue ArgumentError => e
- if $DEBUG
- $stderr.puts " The string:#{ret}"
- $stderr.puts " args:#{args.inspect}"
- puts e.backtrace
- else
- raise ArgumentError, e.message
+ # % uses self (i.e. the String) as a format specification and returns the
+ # result of applying it to the given arguments. In other words it interpolates
+ # the given arguments to the string according to the formats the string
+ # defines.
+ #
+ # There are three ways to use it:
+ #
+ # * Using a single argument or Array of arguments.
+ #
+ # This is the default behaviour of the String class. See Kernel#sprintf for
+ # more details about the format string.
+ #
+ # Example:
+ #
+ # "%d %s" % [1, "message"]
+ # # => "1 message"
+ #
+ # * Using a Hash as an argument and unformatted, named placeholders.
+ #
+ # When you pass a Hash as an argument and specify placeholders with %{foo}
+ # it will interpret the hash values as named arguments.
+ #
+ # Example:
+ #
+ # "%{firstname}, %{lastname}" % {:firstname => "Masao", :lastname => "Mutoh"}
+ # # => "Masao Mutoh"
+ #
+ # * Using a Hash as an argument and formatted, named placeholders.
+ #
+ # When you pass a Hash as an argument and specify placeholders with %<foo>d
+ # it will interpret the hash values as named arguments and format the value
+ # according to the formatting instruction appended to the closing >.
+ #
+ # Example:
+ #
+ # "%<integer>d, %<float>.1f" % { :integer => 10, :float => 43.4 }
+ # # => "10, 43.3"
+ def %(args)
+ if args.kind_of?(Hash)
+ dup.gsub(INTERPOLATION_PATTERN) do |match|
+ if match == '%%'
+ '%'
+ else
+ key = ($1 || $2).to_sym
+ raise KeyError unless args.has_key?(key)
+ $3 ? sprintf("%#{$3}", args[key]) : args[key]
+ end
end
+ elsif self =~ INTERPOLATION_PATTERN
+ raise ArgumentError.new('one hash required')
+ else
+ result = gsub(/%([{<])/, '%%\1')
+ result.send :'interpolate_without_ruby_19_syntax', args
end
end
end
-end
-
end \ No newline at end of file
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index d5282bad6a..c915bf799d 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -19,7 +19,8 @@ module ActiveSupport
class TestCase < ::Test::Unit::TestCase
if defined? MiniTest
Assertion = MiniTest::Assertion
- alias_method :method_name, :name
+ alias_method :method_name, :name if method_defined? :name
+ alias_method :method_name, :__name__ if method_defined? :__name__
else
# TODO: Figure out how to get the Rails::BacktraceFilter into minitest/unit
if defined?(Rails) && ENV['BACKTRACE'].nil?
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index f77ad5236e..a23d3f6fef 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -311,8 +311,8 @@ class TestGetTextString < Test::Unit::TestCase
end
def test_sprintf_lack_argument
- assert_equal("%{num}, test", "%{num}, %{record}" % {:record => "test"})
- assert_equal("%{record}", "%{record}" % {:num => 1})
+ assert_raises(KeyError) { "%{num}, %{record}" % {:record => "test"} }
+ assert_raises(KeyError) { "%{record}" % {:num => 1} }
end
def test_no_placeholder
@@ -336,9 +336,12 @@ class TestGetTextString < Test::Unit::TestCase
assert_equal("foo 1.000000", "%s %f" % ["foo", 1.0])
end
- def test_sprintf_mix
+ def test_sprintf_mix_unformatted_and_formatted_named_placeholders
assert_equal("foo 1.000000", "%{name} %<num>f" % {:name => "foo", :num => 1.0})
- assert_equal("%{name} 1.000000", "%{name} %f" % [1.0])
- assert_equal("%{name} 1.000000", "%{name} %f" % [1.0, 2.0])
+ end
+
+ def test_string_interpolation_raises_an_argument_error_when_mixing_named_and_unnamed_placeholders
+ assert_raises(ArgumentError) { "%{name} %f" % [1.0] }
+ assert_raises(ArgumentError) { "%{name} %f" % [1.0, 2.0] }
end
end
diff --git a/railties/lib/generators/rails/app/templates/script/about.tt b/railties/lib/generators/rails/app/templates/script/about.tt
index 9604485fd2..afbab8b533 100755
--- a/railties/lib/generators/rails/app/templates/script/about.tt
+++ b/railties/lib/generators/rails/app/templates/script/about.tt
@@ -1,4 +1,4 @@
<%= shebang %>
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
$LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info"
require 'commands/about'
diff --git a/railties/lib/generators/rails/app/templates/script/console.tt b/railties/lib/generators/rails/app/templates/script/console.tt
index 307b7c09c4..2f8b95c923 100755
--- a/railties/lib/generators/rails/app/templates/script/console.tt
+++ b/railties/lib/generators/rails/app/templates/script/console.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/console'
diff --git a/railties/lib/generators/rails/app/templates/script/dbconsole.tt b/railties/lib/generators/rails/app/templates/script/dbconsole.tt
index 7ce41645a6..7dce6d16dd 100755
--- a/railties/lib/generators/rails/app/templates/script/dbconsole.tt
+++ b/railties/lib/generators/rails/app/templates/script/dbconsole.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/dbconsole'
diff --git a/railties/lib/generators/rails/app/templates/script/destroy.tt b/railties/lib/generators/rails/app/templates/script/destroy.tt
index 9f22a9dbca..053d3dea39 100755
--- a/railties/lib/generators/rails/app/templates/script/destroy.tt
+++ b/railties/lib/generators/rails/app/templates/script/destroy.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/destroy'
diff --git a/railties/lib/generators/rails/app/templates/script/generate.tt b/railties/lib/generators/rails/app/templates/script/generate.tt
index 9a9e17ab29..ea4a0efd43 100755
--- a/railties/lib/generators/rails/app/templates/script/generate.tt
+++ b/railties/lib/generators/rails/app/templates/script/generate.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/generate'
diff --git a/railties/lib/generators/rails/app/templates/script/performance/benchmarker.tt b/railties/lib/generators/rails/app/templates/script/performance/benchmarker.tt
index 3abf448c15..da9df7fab9 100755
--- a/railties/lib/generators/rails/app/templates/script/performance/benchmarker.tt
+++ b/railties/lib/generators/rails/app/templates/script/performance/benchmarker.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.dirname(__FILE__) + '/../../config/boot'
+require File.expand_path('../../../config/boot', __FILE__)
require 'commands/performance/benchmarker'
diff --git a/railties/lib/generators/rails/app/templates/script/performance/profiler.tt b/railties/lib/generators/rails/app/templates/script/performance/profiler.tt
index 66f851a079..5a0e2b0c28 100755
--- a/railties/lib/generators/rails/app/templates/script/performance/profiler.tt
+++ b/railties/lib/generators/rails/app/templates/script/performance/profiler.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.dirname(__FILE__) + '/../../config/boot'
+require File.expand_path('../../../config/boot', __FILE__)
require 'commands/performance/profiler'
diff --git a/railties/lib/generators/rails/app/templates/script/plugin.tt b/railties/lib/generators/rails/app/templates/script/plugin.tt
index f0603c33c4..68b2148bba 100755
--- a/railties/lib/generators/rails/app/templates/script/plugin.tt
+++ b/railties/lib/generators/rails/app/templates/script/plugin.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/plugin'
diff --git a/railties/lib/generators/rails/app/templates/script/runner.tt b/railties/lib/generators/rails/app/templates/script/runner.tt
index 7302825f6c..a2b313fa32 100755
--- a/railties/lib/generators/rails/app/templates/script/runner.tt
+++ b/railties/lib/generators/rails/app/templates/script/runner.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/runner'
diff --git a/railties/lib/generators/rails/app/templates/script/server.tt b/railties/lib/generators/rails/app/templates/script/server.tt
index 893db31a20..c8868155f3 100755
--- a/railties/lib/generators/rails/app/templates/script/server.tt
+++ b/railties/lib/generators/rails/app/templates/script/server.tt
@@ -1,3 +1,3 @@
<%= shebang %>
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/server'
diff --git a/railties/lib/vendor/bundler/LICENSE b/railties/lib/vendor/bundler/LICENSE
new file mode 100644
index 0000000000..41decca113
--- /dev/null
+++ b/railties/lib/vendor/bundler/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2009 Engine Yard
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/Rakefile b/railties/lib/vendor/bundler/Rakefile
new file mode 100644
index 0000000000..fd39fbff77
--- /dev/null
+++ b/railties/lib/vendor/bundler/Rakefile
@@ -0,0 +1,52 @@
+require 'rubygems' unless ENV['NO_RUBYGEMS']
+require 'rake/gempackagetask'
+require 'rubygems/specification'
+require 'date'
+require 'spec/rake/spectask'
+
+spec = Gem::Specification.new do |s|
+ s.name = "bundler"
+ s.version = "0.0.1"
+ s.author = "Your Name"
+ s.email = "Your Email"
+ s.homepage = "http://example.com"
+ s.description = s.summary = "A gem that provides..."
+
+ s.platform = Gem::Platform::RUBY
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README", "LICENSE"]
+ s.summary = ""
+
+ # Uncomment this to add a dependency
+ # s.add_dependency "foo"
+
+ s.bindir = "bin"
+ s.executables = %w( gem_bundler )
+ s.require_path = 'lib'
+ s.files = %w(LICENSE README Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
+end
+
+task :default => :spec
+
+desc "Run specs"
+Spec::Rake::SpecTask.new do |t|
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ t.spec_opts = %w(-fs --color)
+end
+
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.gem_spec = spec
+end
+
+desc "install the gem locally"
+task :install => [:package] do
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
+end
+
+desc "create a gemspec file"
+task :make_spec do
+ File.open("#{GEM}.gemspec", "w") do |file|
+ file.puts spec.to_ruby
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/bin/gem_bundler b/railties/lib/vendor/bundler/bin/gem_bundler
new file mode 100755
index 0000000000..7a8771efee
--- /dev/null
+++ b/railties/lib/vendor/bundler/bin/gem_bundler
@@ -0,0 +1,40 @@
+#!/usr/bin/env ruby
+require "optparse"
+require "bundler"
+
+options = {}
+
+parser = OptionParser.new do |op|
+ op.banner = "Usage: gem_bundler [OPTIONS] [PATH]"
+
+ op.on("-m", "--manifest MANIFEST") do |manifest|
+ options[:manifest] = manifest
+ end
+
+ op.on_tail("-h", "--help", "Show this message") do
+ puts op
+ exit
+ end
+end
+parser.parse!
+
+options[:path] = ARGV.shift
+
+unless options[:path]
+ puts parser
+ puts %(
+ [PATH] must be specified
+ )
+ exit
+end
+
+unless options[:manifest] && File.exist?(options[:manifest])
+ puts parser
+ puts %(
+ MANIFEST must be a valid manifest file
+ )
+ exit
+end
+
+
+Bundler.run(options) \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler.rb b/railties/lib/vendor/bundler/lib/bundler.rb
new file mode 100644
index 0000000000..4dcab20da9
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler.rb
@@ -0,0 +1,24 @@
+require 'logger'
+require 'set'
+# Required elements of rubygems
+require "rubygems/remote_fetcher"
+require "rubygems/installer"
+
+require "bundler/gem_bundle"
+require "bundler/installer"
+require "bundler/finder"
+require "bundler/gem_specification"
+require "bundler/resolver"
+require "bundler/manifest"
+require "bundler/dependency"
+require "bundler/runtime"
+require "bundler/cli"
+
+module Bundler
+ VERSION = "0.5.0"
+
+ def self.run(options = {})
+ manifest = ManifestBuilder.load(options[:path], options[:manifest])
+ manifest.install
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/cli.rb b/railties/lib/vendor/bundler/lib/bundler/cli.rb
new file mode 100644
index 0000000000..ed49ef88f0
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/cli.rb
@@ -0,0 +1,24 @@
+module Bundler
+ module CLI
+
+ def default_manifest
+ current = Pathname.new(Dir.pwd)
+
+ begin
+ manifest = current.join("Gemfile")
+ return manifest.to_s if File.exist?(manifest)
+ current = current.parent
+ end until current.root?
+ nil
+ end
+
+ module_function :default_manifest
+
+ def default_path
+ Pathname.new(File.dirname(default_manifest)).join("vendor").join("gems").to_s
+ end
+
+ module_function :default_path
+
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/dependency.rb b/railties/lib/vendor/bundler/lib/bundler/dependency.rb
new file mode 100644
index 0000000000..739a7a117d
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/dependency.rb
@@ -0,0 +1,35 @@
+module Bundler
+ class Dependency
+
+ attr_reader :name, :version, :require_as, :only, :except
+
+ def initialize(name, options = {})
+ options.each do |k, v|
+ options[k.to_s] = v
+ end
+
+ @name = name
+ @version = options["version"] || ">= 0"
+ @require_as = Array(options["require_as"] || name)
+ @only = Array(options["only"]).map {|e| e.to_s } if options["only"]
+ @except = Array(options["except"]).map {|e| e.to_s } if options["except"]
+ end
+
+ def in?(environment)
+ environment = environment.to_s
+
+ return false unless !@only || @only.include?(environment)
+ return false if @except && @except.include?(environment)
+ true
+ end
+
+ def to_s
+ to_gem_dependency.to_s
+ end
+
+ def to_gem_dependency
+ @gem_dep ||= Gem::Dependency.new(name, version)
+ end
+
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/finder.rb b/railties/lib/vendor/bundler/lib/bundler/finder.rb
new file mode 100644
index 0000000000..43ff370ae4
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/finder.rb
@@ -0,0 +1,42 @@
+module Bundler
+ class Finder
+ def initialize(*sources)
+ @results = {}
+ @index = Hash.new { |h,k| h[k] = {} }
+
+ sources.each { |source| fetch(source) }
+ end
+
+ def resolve(*dependencies)
+ resolved = Resolver.resolve(dependencies, self)
+ resolved && GemBundle.new(resolved.all_specs)
+ end
+
+ def fetch(source)
+ deflated = Gem::RemoteFetcher.fetcher.fetch_path("#{source}/Marshal.4.8.Z")
+ inflated = Gem.inflate deflated
+
+ append(Marshal.load(inflated), source)
+ rescue Gem::RemoteFetcher::FetchError => e
+ raise ArgumentError, "#{source} is not a valid source: #{e.message}"
+ end
+
+ def append(index, source)
+ index.gems.values.each do |spec|
+ next unless Gem::Platform.match(spec.platform)
+ spec.source = source
+ @index[spec.name][spec.version] ||= spec
+ end
+ self
+ end
+
+ def search(dependency)
+ @results[dependency.hash] ||= begin
+ possibilities = @index[dependency.name].values
+ possibilities.select do |spec|
+ dependency =~ spec
+ end.sort_by {|s| s.version }
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/gem_bundle.rb b/railties/lib/vendor/bundler/lib/bundler/gem_bundle.rb
new file mode 100644
index 0000000000..b749720fd9
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/gem_bundle.rb
@@ -0,0 +1,23 @@
+module Bundler
+ class GemBundle < Array
+ def download(directory)
+ FileUtils.mkdir_p(directory)
+
+ current = Dir[File.join(directory, "cache", "*.gem*")]
+
+ each do |spec|
+ cached = File.join(directory, "cache", "#{spec.full_name}.gem")
+
+ unless File.file?(cached)
+ Gem::RemoteFetcher.fetcher.download(spec, spec.source, directory)
+ end
+
+ current.delete(cached)
+ end
+
+ current.each { |file| File.delete(file) }
+
+ self
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/gem_specification.rb b/railties/lib/vendor/bundler/lib/bundler/gem_specification.rb
new file mode 100644
index 0000000000..680b61fd69
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/gem_specification.rb
@@ -0,0 +1,10 @@
+module Gem
+ class Specification
+ attribute :source
+
+ def source=(source)
+ @source = source.is_a?(URI) ? source : URI.parse(source)
+ raise ArgumentError, "The source must be an absolute URI" unless @source.absolute?
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/installer.rb b/railties/lib/vendor/bundler/lib/bundler/installer.rb
new file mode 100644
index 0000000000..581d9d3c8e
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/installer.rb
@@ -0,0 +1,44 @@
+module Bundler
+ class Installer
+ def initialize(path)
+ if !File.directory?(path)
+ raise ArgumentError, "#{path} is not a directory"
+ elsif !File.directory?(File.join(path, "cache"))
+ raise ArgumentError, "#{path} is not a valid environment (it does not contain a cache directory)"
+ end
+
+ @path = path
+ @gems = Dir[(File.join(path, "cache", "*.gem"))]
+ end
+
+ def install(options = {})
+ bin_dir = options[:bin_dir] ||= File.join(@path, "bin")
+
+ specs = Dir[File.join(@path, "specifications", "*.gemspec")]
+ gems = Dir[File.join(@path, "gems", "*")]
+
+ @gems.each do |gem|
+ name = File.basename(gem).gsub(/\.gem$/, '')
+ installed = specs.any? { |g| File.basename(g) == "#{name}.gemspec" } &&
+ gems.any? { |g| File.basename(g) == name }
+
+ unless installed
+ installer = Gem::Installer.new(gem, :install_dir => @path,
+ :ignore_dependencies => true,
+ :env_shebang => true,
+ :wrappers => true,
+ :bin_dir => bin_dir)
+ installer.install
+ end
+
+ # remove this spec
+ specs.delete_if { |g| File.basename(g) == "#{name}.gemspec"}
+ gems.delete_if { |g| File.basename(g) == name }
+ end
+
+ (specs + gems).each do |path|
+ FileUtils.rm_rf(path)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/manifest.rb b/railties/lib/vendor/bundler/lib/bundler/manifest.rb
new file mode 100644
index 0000000000..847a41940d
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/manifest.rb
@@ -0,0 +1,130 @@
+require "rubygems/source_index"
+require "pathname"
+
+module Bundler
+ class VersionConflict < StandardError; end
+
+ class Manifest
+ attr_reader :sources, :dependencies, :path
+
+ def initialize(sources, dependencies, path)
+ sources.map! {|s| s.is_a?(URI) ? s : URI.parse(s) }
+ @sources, @dependencies, @path = sources, dependencies, Pathname.new(path)
+ end
+
+ def fetch
+ return if all_gems_installed?
+
+ finder = Finder.new(*sources)
+ unless bundle = finder.resolve(*gem_dependencies)
+ gems = @dependencies.map {|d| " #{d.to_s}" }.join("\n")
+ raise VersionConflict, "No compatible versions could be found for:\n#{gems}"
+ end
+
+ bundle.download(@path)
+ end
+
+ def install(options = {})
+ fetch
+ installer = Installer.new(@path)
+ installer.install # options come here
+ create_load_paths_files(File.join(@path, "environments"))
+ create_fake_rubygems(File.join(@path, "environments"))
+ end
+
+ def activate(environment = "default")
+ require File.join(@path, "environments", "#{environment}.rb")
+ end
+
+ def require_all
+ dependencies.each do |dep|
+ dep.require_as.each {|file| require file }
+ end
+ end
+
+ def gems_for(environment)
+ deps = dependencies.select { |d| d.in?(environment) }
+ deps.map! { |d| d.to_gem_dependency }
+ index = Gem::SourceIndex.from_gems_in(File.join(@path, "specifications"))
+ Resolver.resolve(deps, index).all_specs
+ end
+
+ def environments
+ envs = dependencies.map {|dep| Array(dep.only) + Array(dep.except) }.flatten
+ envs << "default"
+ end
+
+ private
+
+ def gem_dependencies
+ @gem_dependencies ||= dependencies.map { |d| d.to_gem_dependency }
+ end
+
+ def all_gems_installed?
+ gem_versions = {}
+
+ Dir[File.join(@path, "cache", "*.gem")].each do |file|
+ file =~ /\/([^\/]+)-([\d\.]+)\.gem$/
+ name, version = $1, $2
+ gem_versions[name] = Gem::Version.new(version)
+ end
+
+ gem_dependencies.all? do |dep|
+ gem_versions[dep.name] &&
+ dep.version_requirements.satisfied_by?(gem_versions[dep.name])
+ end
+ end
+
+ def create_load_paths_files(path)
+ FileUtils.mkdir_p(path)
+ environments.each do |environment|
+ gem_specs = gems_for(environment)
+ File.open(File.join(path, "#{environment}.rb"), "w") do |file|
+ file.puts <<-RUBY_EVAL
+ module Bundler
+ def self.rubygems_required
+ #{create_gem_stubs(path, gem_specs)}
+ end
+ end
+ RUBY_EVAL
+ file.puts "$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))"
+ load_paths_for_specs(gem_specs).each do |load_path|
+ file.puts "$LOAD_PATH.unshift #{load_path.inspect}"
+ end
+ end
+ end
+ end
+
+ def create_gem_stubs(path, gem_specs)
+ gem_specs.map do |spec|
+ path = File.expand_path(File.join(path, '..', 'specifications', "#{spec.full_name}.gemspec"))
+ %{
+ Gem.loaded_specs["#{spec.name}"] = eval(File.read("#{path}"))
+ }
+ end.join("\n")
+ end
+
+ def create_fake_rubygems(path)
+ File.open(File.join(path, "rubygems.rb"), "w") do |file|
+ file.puts <<-RUBY_EVAL
+ $:.delete File.expand_path(File.dirname(__FILE__))
+ load "rubygems.rb"
+ if defined?(Bundler) && Bundler.respond_to?(:rubygems_required)
+ Bundler.rubygems_required
+ end
+ RUBY_EVAL
+ end
+ end
+
+ def load_paths_for_specs(specs)
+ load_paths = []
+ specs.each do |spec|
+ load_paths << File.join(spec.full_gem_path, spec.bindir) if spec.bindir
+ spec.require_paths.each do |path|
+ load_paths << File.join(spec.full_gem_path, path)
+ end
+ end
+ load_paths
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/resolver.rb b/railties/lib/vendor/bundler/lib/bundler/resolver.rb
new file mode 100644
index 0000000000..1ec89e53c4
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/resolver.rb
@@ -0,0 +1,19 @@
+require 'bundler/resolver/inspect'
+require 'bundler/resolver/search'
+require 'bundler/resolver/engine'
+require 'bundler/resolver/stack'
+require 'bundler/resolver/state'
+
+module Bundler
+ module Resolver
+ def self.resolve(deps, source_index = Gem.source_index, logger = nil)
+ unless logger
+ logger = Logger.new($stderr)
+ logger.datetime_format = ""
+ logger.level = ENV["GEM_RESOLVER_DEBUG"] ? Logger::DEBUG : Logger::ERROR
+ end
+
+ Engine.resolve(deps, source_index, logger)
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/resolver/builders.rb b/railties/lib/vendor/bundler/lib/bundler/resolver/builders.rb
new file mode 100644
index 0000000000..2b7b48211c
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/resolver/builders.rb
@@ -0,0 +1,61 @@
+module Bundler
+ module Resolver
+ module Builders
+ def build_index(&block)
+ index = Gem::SourceIndex.new
+ IndexBuilder.run(index, &block) if block_given?
+ index
+ end
+
+ def build_spec(name, version, &block)
+ spec = Gem::Specification.new
+ spec.instance_variable_set(:@name, name)
+ spec.instance_variable_set(:@version, Gem::Version.new(version))
+ DepBuilder.run(spec, &block) if block_given?
+ spec
+ end
+
+ def build_dep(name, requirements, type = :runtime)
+ Gem::Dependency.new(name, requirements, type)
+ end
+
+ class IndexBuilder
+ include Builders
+
+ def self.run(index, &block)
+ new(index).run(&block)
+ end
+
+ def initialize(index)
+ @index = index
+ end
+
+ def run(&block)
+ instance_eval(&block)
+ end
+
+ def add_spec(*args, &block)
+ @index.add_spec(build_spec(*args, &block))
+ end
+ end
+
+ class DepBuilder
+ def self.run(spec, &block)
+ new(spec).run(&block)
+ end
+
+ def initialize(spec)
+ @spec = spec
+ end
+
+ def run(&block)
+ instance_eval(&block)
+ end
+
+ def runtime(name, requirements)
+ @spec.add_runtime_dependency(name, requirements)
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/resolver/engine.rb b/railties/lib/vendor/bundler/lib/bundler/resolver/engine.rb
new file mode 100644
index 0000000000..475ba516ff
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/resolver/engine.rb
@@ -0,0 +1,38 @@
+module Bundler
+ module Resolver
+ class ClosedSet < Set
+ end
+
+ class Engine
+ include Search, Inspect
+
+ def self.resolve(deps, source_index, logger)
+ new(deps, source_index, logger).resolve
+ end
+
+ def initialize(deps, source_index, logger)
+ @deps, @source_index, @logger = deps, source_index, logger
+ logger.debug "searching for #{gem_resolver_inspect(@deps)}"
+ end
+ attr_reader :deps, :source_index, :logger, :solution
+
+ def resolve
+ state = State.initial(self, [], Stack.new, Stack.new([[[], @deps.dup]]))
+ if solution = search(state)
+ logger.info "got the solution with #{solution.all_specs.size} specs"
+ solution.dump(Logger::INFO)
+ solution
+ end
+ end
+
+ def open
+ @open ||= []
+ end
+
+ def closed
+ @closed ||= ClosedSet.new
+ end
+ end
+ end
+
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/resolver/inspect.rb b/railties/lib/vendor/bundler/lib/bundler/resolver/inspect.rb
new file mode 100644
index 0000000000..59640aa5f3
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/resolver/inspect.rb
@@ -0,0 +1,24 @@
+module Bundler
+ module Resolver
+ module Inspect
+ def gem_resolver_inspect(o)
+ case o
+ when Gem::Specification
+ "#<Spec: #{o.full_name}>"
+ when Array
+ '[' + o.map {|x| gem_resolver_inspect(x)}.join(", ") + ']'
+ when Set
+ gem_resolver_inspect(o.to_a)
+ when Hash
+ '{' + o.map {|k,v| "#{gem_resolver_inspect(k)} => #{gem_resolver_inspect(v)}"}.join(", ") + '}'
+ when Stack
+ o.gem_resolver_inspect
+ else
+ o.inspect
+ end
+ end
+
+ module_function :gem_resolver_inspect
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/resolver/search.rb b/railties/lib/vendor/bundler/lib/bundler/resolver/search.rb
new file mode 100644
index 0000000000..34102ff04c
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/resolver/search.rb
@@ -0,0 +1,71 @@
+module Bundler
+ module Resolver
+ module Search
+ def search(initial, max_depth = (1.0 / 0.0))
+ if initial.goal_met?
+ return initial
+ end
+
+ open << initial
+
+ while open.any?
+ current = open.pop
+ closed << current
+
+ new = []
+ current.each_possibility do |attempt|
+ unless closed.include?(attempt)
+ if attempt.goal_met?
+ return attempt
+ elsif attempt.depth < max_depth
+ new << attempt
+ end
+ end
+ end
+ new.reverse.each do |state|
+ open << state
+ end
+ end
+
+ nil
+ end
+
+ def open
+ raise "implement #open in #{self.class}"
+ end
+
+ def closed
+ raise "implement #closed in #{self.class}"
+ end
+
+ module Node
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def initial(*data)
+ new(0, *data)
+ end
+ end
+
+ def initialize(depth)
+ @depth = depth
+ end
+ attr_reader :depth
+
+ def child(*data)
+ self.class.new(@depth + 1, *data)
+ end
+
+ def each_possibility
+ raise "implement #each_possibility on #{self.class}"
+ end
+
+ def goal_met?
+ raise "implement #goal_met? on #{self.class}"
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/resolver/stack.rb b/railties/lib/vendor/bundler/lib/bundler/resolver/stack.rb
new file mode 100644
index 0000000000..6e1ac67e1f
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/resolver/stack.rb
@@ -0,0 +1,72 @@
+module Bundler
+ module Resolver
+ class Stack
+ def initialize(initial = [])
+ @data = []
+ initial.each do |(path,value)|
+ self[path] = value
+ end
+ end
+
+ def last
+ @data.last
+ end
+
+ def []=(path, value)
+ raise ArgumentError, "#{path.inspect} already has a value" if key?(path)
+ @data << [path.dup, value]
+ end
+
+ def [](path)
+ if key?(path)
+ _, value = @data.find do |(k,v)|
+ k == path
+ end
+ value
+ else
+ raise "No value for #{path.inspect}"
+ end
+ end
+
+ def key?(path)
+ @data.any? do |(k,v)|
+ k == path
+ end
+ end
+
+ def each
+ @data.each do |(k,v)|
+ yield k, v
+ end
+ end
+
+ def map
+ @data.map do |(k,v)|
+ yield k, v
+ end
+ end
+
+ def each_value
+ @data.each do |(k,v)|
+ yield v
+ end
+ end
+
+ def dup
+ self.class.new(@data.dup)
+ end
+
+ def to_s
+ @data.to_s
+ end
+
+ def inspect
+ @data.inspect
+ end
+
+ def gem_resolver_inspect
+ Inspect.gem_resolver_inspect(@data)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/resolver/state.rb b/railties/lib/vendor/bundler/lib/bundler/resolver/state.rb
new file mode 100644
index 0000000000..f13ecbbee7
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/resolver/state.rb
@@ -0,0 +1,172 @@
+module Bundler
+ module Resolver
+ class State
+ include Search::Node, Inspect
+
+ def initialize(depth, engine, path, spec_stack, dep_stack)
+ super(depth)
+ @engine, @path, @spec_stack, @dep_stack = engine, path, spec_stack, dep_stack
+ end
+ attr_reader :path
+
+ def logger
+ @engine.logger
+ end
+
+ def goal_met?
+ logger.info "checking if goal is met"
+ dump
+ no_duplicates?
+ all_deps.all? do |dep|
+ dependency_satisfied?(dep)
+ end
+ end
+
+ def no_duplicates?
+ names = []
+ all_specs.each do |s|
+ if names.include?(s.name)
+ raise "somehow got duplicates for #{s.name}"
+ end
+ names << s.name
+ end
+ end
+
+ def dependency_satisfied?(dep)
+ all_specs.any? do |spec|
+ spec.satisfies_requirement?(dep)
+ end
+ end
+
+ def each_possibility(&block)
+ index, dep = remaining_deps.first
+ if dep
+ logger.warn "working on #{dep} for #{spec_name}"
+ handle_dep(index, dep, &block)
+ else
+ logger.warn "no dependencies left for #{spec_name}"
+ jump_to_parent(&block)
+ end
+ end
+
+ def handle_dep(index, dep)
+ specs = @engine.source_index.search(dep)
+
+ specs.reverse.each do |s|
+ logger.info "attempting with spec: #{s.full_name}"
+ new_path = @path + [index]
+ new_spec_stack = @spec_stack.dup
+ new_dep_stack = @dep_stack.dup
+
+ new_spec_stack[new_path] = s
+ new_dep_stack[new_path] = s.runtime_dependencies.sort_by do |dep|
+ @engine.source_index.search(dep).size
+ end
+ yield child(@engine, new_path, new_spec_stack, new_dep_stack)
+ end
+ end
+
+ def jump_to_parent
+ if @path.empty?
+ dump
+ logger.warn "at the end"
+ return
+ end
+
+ logger.info "jumping to parent for #{spec_name}"
+ new_path = @path[0..-2]
+ new_spec_stack = @spec_stack.dup
+ new_dep_stack = @dep_stack.dup
+
+ yield child(@engine, new_path, new_spec_stack, new_dep_stack)
+ end
+
+ def remaining_deps
+ remaining_deps_for(@path)
+ end
+
+ def remaining_deps_for(path)
+ no_duplicates?
+ remaining = []
+ @dep_stack[path].each_with_index do |dep,i|
+ remaining << [i, dep] unless all_specs.find {|s| s.name == dep.name}
+ end
+ remaining
+ end
+
+ def deps
+ @dep_stack[@path]
+ end
+
+ def spec
+ @spec_stack[@path]
+ end
+
+ def spec_name
+ @path.empty? ? "<top>" : spec.full_name
+ end
+
+ def all_deps
+ all_deps = Set.new
+ @dep_stack.each_value do |deps|
+ all_deps.merge(deps)
+ end
+ all_deps.to_a
+ end
+
+ def all_specs
+ @spec_stack.map do |path,spec|
+ spec
+ end
+ end
+
+ def dump(level = Logger::DEBUG)
+ logger.add level, "v" * 80
+ logger.add level, "path: #{@path.inspect}"
+ logger.add level, "deps: (#{deps.size})"
+ deps.map do |dep|
+ logger.add level, gem_resolver_inspect(dep)
+ end
+ logger.add level, "remaining_deps: (#{remaining_deps.size})"
+ remaining_deps.each do |dep|
+ logger.add level, gem_resolver_inspect(dep)
+ end
+ logger.add level, "dep_stack: "
+ @dep_stack.each do |path,deps|
+ logger.add level, "#{path.inspect} (#{deps.size})"
+ deps.each do |dep|
+ logger.add level, "-> #{gem_resolver_inspect(dep)}"
+ end
+ end
+ logger.add level, "spec_stack: "
+ @spec_stack.each do |path,spec|
+ logger.add level, "#{path.inspect}: #{gem_resolver_inspect(spec)}"
+ end
+ logger.add level, "^" * 80
+ end
+
+ def to_dot
+ io = StringIO.new
+ io.puts 'digraph deps {'
+ io.puts ' fontname = "Courier";'
+ io.puts ' mincross = 4.0;'
+ io.puts ' ratio = "auto";'
+ dump_to_dot(io, "<top>", [])
+ io.puts '}'
+ io.string
+ end
+
+ def dump_to_dot(io, name, path)
+ @dep_stack[path].each_with_index do |dep,i|
+ new_path = path + [i]
+ spec_name = all_specs.find {|x| x.name == dep.name}.full_name
+ io.puts ' "%s" -> "%s";' % [name, dep.to_s]
+ io.puts ' "%s" -> "%s";' % [dep.to_s, spec_name]
+ if @spec_stack.key?(new_path)
+ dump_to_dot(io, spec_name, new_path)
+ end
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/vendor/bundler/lib/bundler/runtime.rb b/railties/lib/vendor/bundler/lib/bundler/runtime.rb
new file mode 100644
index 0000000000..2376deef28
--- /dev/null
+++ b/railties/lib/vendor/bundler/lib/bundler/runtime.rb
@@ -0,0 +1,39 @@
+module Bundler
+ class ManifestBuilder
+
+ attr_reader :sources
+
+ def self.build(path, string)
+ builder = new(path)
+ builder.instance_eval(string)
+ builder.to_manifest
+ end
+
+ def self.load(path, file)
+ string = File.read(file)
+ build(path, string)
+ end
+
+ def initialize(path)
+ @path = path
+ @sources = %w(http://gems.rubyforge.org)
+ @dependencies = []
+ end
+
+ def to_manifest
+ Manifest.new(@sources, @dependencies, @path)
+ end
+
+ def source(source)
+ @sources << source
+ end
+
+ def gem(name, *args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ version = args.last
+
+ @dependencies << Dependency.new(name, options.merge(:version => version))
+ end
+
+ end
+end \ No newline at end of file