Permalink
Browse files

Merge commit 'rails/master'

  • Loading branch information...
2 parents 9a28bd7 + 9d7aae7 commit f32c3709830eb8d9f68a59c94f6791621c2b52ac @miloops miloops committed Jul 21, 2009
Showing with 1,319 additions and 167 deletions.
  1. +1 −0 actionpack/lib/action_view/helpers.rb
  2. +68 −0 actionpack/lib/action_view/helpers/ajax_helper.rb
  3. +2 −1 actionpack/test/activerecord/render_partial_with_record_identification_test.rb
  4. +2 −1 actionpack/test/controller/record_identifier_test.rb
  5. +2 −1 actionpack/test/controller/redirect_test.rb
  6. +115 −0 actionpack/test/javascript/ajax_test.rb
  7. +4 −2 actionpack/test/lib/controller/fake_models.rb
  8. +6 −3 actionpack/test/template/active_record_helper_test.rb
  9. +2 −1 actionpack/test/template/atom_feed_helper_test.rb
  10. +10 −5 actionpack/test/template/form_helper_test.rb
  11. +8 −4 actionpack/test/template/prototype_helper_test.rb
  12. +2 −1 actionpack/test/template/record_tag_helper_test.rb
  13. +1 −1 actionpack/test/template/test_test.rb
  14. +4 −2 actionpack/test/template/url_helper_test.rb
  15. +8 −15 activemodel/examples/{amo_ap_example.rb → validations.rb}
  16. +1 −3 activemodel/lib/active_model.rb
  17. +0 −25 activemodel/lib/active_model/api_compliant.rb
  18. +0 −25 activemodel/lib/active_model/attributes.rb
  19. +0 −8 activemodel/lib/active_model/base.rb
  20. +8 −0 activemodel/lib/active_model/conversion.rb
  21. +92 −5 activemodel/lib/active_model/observing.rb
  22. +0 −1 activemodel/lib/active_model/serializers/json.rb
  23. +0 −1 activemodel/lib/active_model/serializers/xml.rb
  24. +1 −0 activemodel/lib/activemodel.rb
  25. +0 −30 activemodel/test/cases/attributes_test.rb
  26. +5 −2 activemodel/test/cases/observing_test.rb
  27. +4 −0 activemodel/test/cases/serializeration/json_serialization_test.rb
  28. +4 −0 activemodel/test/cases/serializeration/xml_serialization_test.rb
  29. +1 −7 activerecord/lib/active_record/base.rb
  30. +1 −1 activerecord/lib/active_record/callbacks.rb
  31. +1 −0 activeresource/lib/active_resource.rb
  32. +3 −10 activeresource/lib/active_resource/base.rb
  33. +10 −0 activeresource/lib/active_resource/observing.rb
  34. +2 −1 activesupport/lib/active_support/test_case.rb
  35. +2 −2 railties/bin/about
  36. +1 −1 railties/bin/console
  37. +1 −1 railties/bin/dbconsole
  38. +1 −1 railties/bin/destroy
  39. +1 −1 railties/bin/generate
  40. +1 −1 railties/bin/performance/benchmarker
  41. +1 −1 railties/bin/performance/profiler
  42. +1 −1 railties/bin/plugin
  43. +1 −1 railties/bin/runner
  44. +1 −1 railties/bin/server
  45. +20 −0 railties/lib/vendor/bundler/LICENSE
  46. +52 −0 railties/lib/vendor/bundler/Rakefile
  47. +40 −0 railties/lib/vendor/bundler/bin/gem_bundler
  48. +24 −0 railties/lib/vendor/bundler/lib/bundler.rb
  49. +24 −0 railties/lib/vendor/bundler/lib/bundler/cli.rb
  50. +35 −0 railties/lib/vendor/bundler/lib/bundler/dependency.rb
  51. +42 −0 railties/lib/vendor/bundler/lib/bundler/finder.rb
  52. +23 −0 railties/lib/vendor/bundler/lib/bundler/gem_bundle.rb
  53. +10 −0 railties/lib/vendor/bundler/lib/bundler/gem_specification.rb
  54. +44 −0 railties/lib/vendor/bundler/lib/bundler/installer.rb
  55. +130 −0 railties/lib/vendor/bundler/lib/bundler/manifest.rb
  56. +19 −0 railties/lib/vendor/bundler/lib/bundler/resolver.rb
  57. +61 −0 railties/lib/vendor/bundler/lib/bundler/resolver/builders.rb
  58. +38 −0 railties/lib/vendor/bundler/lib/bundler/resolver/engine.rb
  59. +24 −0 railties/lib/vendor/bundler/lib/bundler/resolver/inspect.rb
  60. +71 −0 railties/lib/vendor/bundler/lib/bundler/resolver/search.rb
  61. +72 −0 railties/lib/vendor/bundler/lib/bundler/resolver/stack.rb
  62. +172 −0 railties/lib/vendor/bundler/lib/bundler/resolver/state.rb
  63. +39 −0 railties/lib/vendor/bundler/lib/bundler/runtime.rb
View
1 actionpack/lib/action_view/helpers.rb
@@ -1,6 +1,7 @@
module ActionView #:nodoc:
module Helpers #:nodoc:
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'
View
68 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
View
3 actionpack/test/activerecord/render_partial_with_record_identification_test.rb
@@ -126,7 +126,8 @@ def render_with_record_collection
end
class Game < Struct.new(:name, :id)
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
def to_param
id.to_s
end
View
3 actionpack/test/controller/record_identifier_test.rb
@@ -1,7 +1,8 @@
require 'abstract_unit'
class Comment
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_reader :id
def save; @id = 1 end
View
3 actionpack/test/controller/redirect_test.rb
@@ -4,7 +4,8 @@ class WorkshopsController < ActionController::Base
end
class Workshop
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_accessor :id, :new_record
def initialize(id, new_record)
View
115 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
View
6 actionpack/test/lib/controller/fake_models.rb
@@ -1,7 +1,8 @@
require "active_model"
class Customer < Struct.new(:name, :id)
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
def to_param
id.to_s
@@ -16,7 +17,8 @@ class GoodCustomer < Customer
module Quiz
class Question < Struct.new(:name, :id)
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
def to_param
id.to_s
View
9 actionpack/test/template/active_record_helper_test.rb
@@ -5,15 +5,18 @@ class ActiveRecordHelperTest < ActionView::TestCase
silence_warnings do
class Post < Struct.new(:title, :author_name, :body, :secret, :written_on)
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
end
class User < Struct.new(:email)
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
end
class Column < Struct.new(:type, :name, :human_name)
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
end
end
View
3 actionpack/test/template/atom_feed_helper_test.rb
@@ -1,7 +1,8 @@
require 'abstract_unit'
class Scroll < Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at)
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
def new_record?
true
View
15 actionpack/test/template/form_helper_test.rb
@@ -2,7 +2,8 @@
silence_warnings do
class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost)
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
alias_method :secret?, :secret
@@ -25,7 +26,8 @@ def tags_attributes=(attributes); end
end
class Comment
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_reader :id
attr_reader :post_id
@@ -43,7 +45,8 @@ def relevances_attributes=(attributes); end
end
class Tag
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_reader :id
attr_reader :post_id
@@ -61,7 +64,8 @@ def relevances_attributes=(attributes); end
end
class CommentRelevance
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_reader :id
attr_reader :comment_id
@@ -75,7 +79,8 @@ def value
end
class TagRelevance
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_reader :id
attr_reader :tag_id
View
12 actionpack/test/template/prototype_helper_test.rb
@@ -1,11 +1,14 @@
require 'abstract_unit'
require 'active_model'
-Bunny = Struct.new(:Bunny, :id)
-Bunny.extend ActiveModel::APICompliant
+class Bunny < Struct.new(:Bunny, :id)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+end
class Author
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_reader :id
def save; @id = 1 end
@@ -16,7 +19,8 @@ def name
end
class Article
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_reader :id
attr_reader :author_id
def save; @id = 1; @author_id = 1 end
View
3 actionpack/test/template/record_tag_helper_test.rb
@@ -1,7 +1,8 @@
require 'abstract_unit'
class Post
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
def id
45
end
View
2 actionpack/test/template/test_test.rb
@@ -41,7 +41,7 @@ def test_homepage_url
def test_link_to_person
person = mock(:name => "David")
- person.class.extend ActiveModel::APICompliant
+ person.class.extend ActiveModel::Naming
expects(:mocha_mock_path).with(person).returns("/people/1")
assert_equal '<a href="/people/1">David</a>', link_to_person(person)
end
View
6 actionpack/test/template/url_helper_test.rb
@@ -494,7 +494,8 @@ def with_restful_routing
end
class Workshop
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_accessor :id, :new_record
def initialize(id, new_record)
@@ -511,7 +512,8 @@ def to_s
end
class Session
- extend ActiveModel::APICompliant
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
attr_accessor :id, :workshop_id, :new_record
def initialize(id, new_record)
View
23 activemodel/examples/amo_ap_example.rb → activemodel/examples/validations.rb
@@ -1,36 +1,29 @@
-$:.push "activesupport/lib"
-$:.push "activemodel/lib"
-
-require "active_model/validations"
-require "active_model/deprecated_error_methods"
-require "active_model/errors"
-require "active_model/naming"
+require 'activemodel'
class Person
+ include ActiveModel::Conversion
include ActiveModel::Validations
- extend ActiveModel::Naming
-
+
validates_presence_of :name
-
+
attr_accessor :name
+
def initialize(attributes = {})
@name = attributes[:name]
end
-
+
def persist
@persisted = true
end
-
+
def new_record?
@persisted
end
-
- def to_model() self end
end
person1 = Person.new
p person1.valid?
person1.errors
person2 = Person.new(:name => "matz")
-p person2.valid?
+p person2.valid?
View
4 activemodel/lib/active_model.rb
@@ -26,9 +26,7 @@
require 'active_support'
module ActiveModel
- autoload :APICompliant, 'active_model/api_compliant'
- 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'
View
25 activemodel/lib/active_model/api_compliant.rb
@@ -1,25 +0,0 @@
-module ActiveModel
- module APICompliant
- include Naming
-
- def self.extended(klass)
- klass.class_eval do
- include Validations
- include InstanceMethods
- end
- end
-
- module InstanceMethods
- def to_model
- if respond_to?(:new_record?)
- self.class.class_eval { def to_model() self end }
- to_model
- else
- raise "In order to be ActiveModel API compliant, you need to define " \
- "a new_record? method, which should return true if it has not " \
- "yet been persisted."
- end
- end
- end
- end
-end
View
25 activemodel/lib/active_model/attributes.rb
@@ -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
View
8 activemodel/lib/active_model/base.rb
@@ -1,8 +0,0 @@
-module ActiveModel
- class Base
- include Observing
- # disabled, until they're tested
- # include Callbacks
- # include Validations
- end
-end
View
8 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
View
97 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 @@ def instantiate_observers
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 @@ def inherited(subclass)
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 @@ def observe(*models)
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 @@ def initialize
observed_classes.each { |klass| add_observer!(klass) }
end
- def observed_classes
+ def observed_classes #:nodoc:
self.class.observed_classes
end
@@ -114,7 +201,7 @@ def observed_class_inherited(subclass) #:nodoc:
end
protected
- def add_observer!(klass)
+ def add_observer!(klass) #:nodoc:
klass.add_observer(self)
end
end
View
1 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
View
1 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:
View
1 activemodel/lib/activemodel.rb
@@ -0,0 +1 @@
+require 'active_model'
View
30 activemodel/test/cases/attributes_test.rb
@@ -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
View
7 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 @@ def on_spec(record)
end
end
-class Foo < ActiveModel::Base
+class Foo
+ include ActiveModel::Observing
end
class ObservingTest < ActiveModel::TestCase
View
4 activemodel/test/cases/serializeration/json_serialization_test.rb
@@ -3,6 +3,10 @@
class Contact
include ActiveModel::Serializers::JSON
+
+ def attributes
+ instance_values
+ end
end
class JsonSerializationTest < ActiveModel::TestCase
View
4 activemodel/test/cases/serializeration/xml_serialization_test.rb
@@ -3,6 +3,10 @@
class Contact
include ActiveModel::Serializers::Xml
+
+ def attributes
+ instance_values
+ end
end
class XmlSerializationTest < ActiveModel::TestCase
View
8 activerecord/lib/active_record/base.rb
@@ -2528,13 +2528,6 @@ def to_param
(id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
end
- # Returns the ActiveRecord object when asked for its
- # ActiveModel-compliant representation, because ActiveRecord is
- # ActiveModel-compliant.
- def to_model
- self
- end
-
# Returns a cache key that can be used to identify this record.
#
# ==== Examples
@@ -3215,6 +3208,7 @@ def clone_attribute_value(reader_method, attribute_name)
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.
View
2 activerecord/lib/active_record/callbacks.rb
@@ -349,7 +349,7 @@ def callback(method)
result = send(method)
end
- notify(method)
+ notify_observers(method)
return result
end
View
1 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
View
13 activeresource/lib/active_resource/base.rb
@@ -804,8 +804,7 @@ def dup
# 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 @@ def save
# 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 @@ def connection(refresh = false)
# 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 @@ def method_missing(method_symbol, *arguments) #:nodoc:
class Base
extend ActiveModel::Naming
- include CustomMethods, Validations
- include ActiveModel::Observing
+ include CustomMethods, Observing, Validations
end
end
View
10 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
View
3 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?
View
4 railties/bin/about
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
$LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info"
-require 'commands/about'
+require 'commands/about'
View
2 railties/bin/console
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/console'
View
2 railties/bin/dbconsole
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/dbconsole'
View
2 railties/bin/destroy
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/destroy'
View
2 railties/bin/generate
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/generate'
View
2 railties/bin/performance/benchmarker
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../../config/boot'
+require File.expand_path('../../../config/boot', __FILE__)
require 'commands/performance/benchmarker'
View
2 railties/bin/performance/profiler
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../../config/boot'
+require File.expand_path('../../../config/boot', __FILE__)
require 'commands/performance/profiler'
View
2 railties/bin/plugin
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/plugin'
View
2 railties/bin/runner
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/runner'
View
2 railties/bin/server
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../config/boot'
+require File.expand_path('../../config/boot', __FILE__)
require 'commands/server'
View
20 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.
View
52 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
View
40 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)
View
24 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
View
24 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
View
35 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
View
42 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
View
23 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
View
10 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
View
44 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
View
130 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
View
19 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
View
61 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
View
38 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
View
24 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
View
71 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
View
72 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
View
172 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
View
39 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

0 comments on commit f32c370

Please sign in to comment.