提交 33048b6e 编写于 作者: J José Valim

Solving merge conflicts.

$:.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
+ <%= yield %> +
\ No newline at end of file
Hello <%= yield %> Goodbye
\ No newline at end of file
Hello <%= @name %>
\ No newline at end of file
......@@ -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
require 'active_support/core_ext/module/attr_internal'
module AbstractController
class Base
......
require "active_support/new_callbacks"
module AbstractController
module Callbacks
extend ActiveSupport::Concern
......
......@@ -8,12 +8,6 @@ module Helpers
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
......
......@@ -6,6 +6,7 @@ module Layouts
included do
extlib_inheritable_accessor(:_layout_conditions) { Hash.new }
_write_layout_method
end
module ClassMethods
......@@ -99,7 +100,7 @@ def _layout(details) end
# 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 @@ def _layout_for_name(name, details = {:formats => formats})
#
# ==== 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)
......
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/logger'
module AbstractController
......
......@@ -17,25 +17,22 @@ module Renderer
# 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.
......
......@@ -114,7 +114,7 @@ def method_for_action(action_name)
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 @@ def _find_by_parts(name, details)
def _default_layout(details, require_layout = false)
super
rescue ActionView::MissingTemplate
_find_by_parts(_layout({}), {})
_find_layout(_layout({}), {})
nil
end
......
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/name_error'
require 'active_support/dependencies'
module ActionController
......
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.
......
......@@ -77,6 +77,7 @@ def polymorphic_url(record_or_hash_or_array, options = {})
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
......
require 'action_dispatch'
require 'rack/session/abstract/id'
require 'active_support/core_ext/object/conversions'
......
......@@ -30,12 +30,14 @@
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'
......
......@@ -172,8 +172,6 @@ class Base
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 @@ def self.cache_template_loading?
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 @@ def include(*args)
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) }
......
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
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 ClassMethods
include SanitizeHelper::ClassMethods
end
include ActiveRecordHelper
include ActiveModelHelper
include AssetTagHelper
include AtomFeedHelper
include BenchmarkHelper
......
......@@ -15,7 +15,7 @@ module Helpers
# 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 @@ def input(record_name, method, options = {})
# * <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 @@ def error_message_on(object, method, *args)
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 @@ def error_messages_for(*params)
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 @@ def default_input_block
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 @@ def to_tag(options = {})
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 @@ def column_type
object.send(:column_for_attribute, @method_name).type
end
end
class InstanceTag
include ActiveRecordInstanceTag
end
end
end
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
......@@ -288,6 +288,8 @@ def form_for(record_or_name_or_array, *args, &proc)
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 @@ def fields_for(record_or_name_or_array, *args, &block)
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 @@ def text_area(object_name, method, options = {})
# 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 @@ def radio_button(object_name, method, tag_value, options = {})
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 @@ def value_before_type_cast(object)
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 @@ def sanitized_method_name
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 @@ def nested_attributes_association?(association_name)
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 @@ def nested_child_index(name)
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
......@@ -259,7 +259,7 @@ def _render_partial_object(template, options, object = nil)
_set_locals(object, locals, template, options)
self._partial = template
options[:_template] = template
_render_template(template, locals)
end
......@@ -278,7 +278,7 @@ def _render_partial_collection(collection, options = {}, passed_template = nil)
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 @@ def _render_partial_collection(collection, options = {}, passed_template = nil)
index += 1
self._partial = template
_render_template(template, locals)
end.join(spacer)
end
......
......@@ -12,9 +12,9 @@ def render(view, locals)
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 @@ def method_name(local_assigns)
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 @@ def #{render_symbol}(local_assigns)
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}"
......
......@@ -75,7 +75,7 @@ def #{method_name}(local_assigns)
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)
......
......@@ -23,6 +23,7 @@ def _render_template(template, local_assigns = {})
class TestCase < ActiveSupport::TestCase
include ActionDispatch::Assertions
include ActionController::TestProcess
include ActionView::Context
class_inheritable_accessor :helper_class
@@helper_class = nil
......
......@@ -20,6 +20,7 @@
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 @@ def assert_template(options = {}, message = nil)
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)
......
......@@ -127,6 +127,7 @@ def render_with_record_collection
class Game < Struct.new(:name, :id)
extend ActiveModel::Naming
include ActiveModel::Conversion
def to_param
id.to_s
end
......
......@@ -2,6 +2,7 @@
class Comment
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
def save; @id = 1 end
......
......@@ -5,6 +5,7 @@ class WorkshopsController < ActionController::Base
class Workshop
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_accessor :id, :new_record
def initialize(id, new_record)
......
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
......@@ -2,6 +2,7 @@
class Customer < Struct.new(:name, :id)
extend ActiveModel::Naming
include ActiveModel::Conversion
def to_param
id.to_s
......@@ -17,6 +18,7 @@ class GoodCustomer < Customer
module Quiz
class Question < Struct.new(:name, :id)
extend ActiveModel::Naming
include ActiveModel::Conversion
def to_param
id.to_s
......
require 'abstract_unit'
class ActiveRecordHelperI18nTest < Test::Unit::TestCase
include ActionView::Helpers::ActiveRecordHelper
include ActionView::Context
include ActionView::Helpers::ActiveModelHelper
attr_reader :request
......
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
......
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 = {}
......
......@@ -3,7 +3,7 @@
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
......
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 @@ def tags_attributes=(attributes); end
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 @@ def relevances_attributes=(attributes); end
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 @@ def relevances_attributes=(attributes); end
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 @@ def value
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 @@ def test_form_for_with_labelled_builder
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 @@ def test_default_form_builder
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
......
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 @@ def name
class Article
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :author_id
def save; @id = 1; @author_id = 1 end
......
......@@ -2,11 +2,12 @@
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
......
......@@ -495,6 +495,7 @@ def with_restful_routing
class Workshop
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_accessor :id, :new_record
def initialize(id, new_record)
......@@ -512,6 +513,7 @@ def to_s
class Session
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_accessor :id, :workshop_id, :new_record
def initialize(id, new_record)
......
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?
......@@ -26,8 +26,7 @@
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'
......
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
module ActiveModel
class Base
include Observing
# disabled, until they're tested
# include Callbacks
# include Validations
end
end
\ No newline at end of file
module ActiveModel
# Include ActiveModel::Conversion if your object "acts like an ActiveModel model".
module Conversion
def to_model
self
end
end
end
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
......
......@@ -5,7 +5,6 @@ module ActiveModel
module Serializers
module JSON
extend ActiveSupport::Concern
include ActiveModel::Attributes
included do
extend ActiveModel::Naming
......
......@@ -5,7 +5,6 @@ module ActiveModel
module Serializers
module Xml
extend ActiveSupport::Concern
include ActiveModel::Attributes
class Serializer < ActiveModel::Serializer #:nodoc:
class Attribute #:nodoc:
......
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
......
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
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
......
......@@ -3,6 +3,10 @@
class Contact
include ActiveModel::Serializers::JSON
def attributes
instance_values
end
end
class JsonSerializationTest < ActiveModel::TestCase
......
......@@ -3,6 +3,10 @@
class Contact
include ActiveModel::Serializers::Xml
def attributes
instance_values
end
end
class XmlSerializationTest < ActiveModel::TestCase
......
*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]
......
......@@ -963,6 +963,8 @@ def has_one(association_id, options = {})
# 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 @@ def has_one(association_id, options = {})
#
# 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 @@ def add_counter_cache_callbacks(reflection)
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 @@ def create_has_one_through_reflection(association_id, options)
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
]
......
......@@ -14,7 +14,7 @@ def replace(record)
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 @@ def replace(record)
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 @@ def updated?
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 @@ def foreign_key_present
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
......@@ -7,7 +7,7 @@ def replace(record)
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 @@ def foreign_key_present
!@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
......
......@@ -339,7 +339,8 @@ def save_belongs_to_association(reflection)
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
......
......@@ -3179,6 +3179,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.
......
......@@ -349,7 +349,7 @@ def callback(method)
result = send(method)
end
notify(method)
notify_observers(method)
return result
end
......
......@@ -14,6 +14,7 @@
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 @@ def test_belongs_to
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 @@ def test_natural_assignment
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 @@ def test_creating_the_belonging_object
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 @@ def test_building_the_belonging_object
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 @@ def test_natural_assignment_to_nil
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 @@ def test_belongs_to_counter
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 @@ def test_belongs_to_counter_with_assigning_nil
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 @@ def test_assignment_before_child_saved
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 @@ def test_polymorphic_assignment_foreign_type_field_updating
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 @@ def test_polymorphic_assignment_updates_foreign_id_field_for_new_and_saved_recor
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 }
......
......@@ -2026,7 +2026,7 @@ def test_inspect_class
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
......
......@@ -27,25 +27,25 @@ def test_column_null_not_null
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
......
......@@ -87,6 +87,8 @@ def testing_proxy_target
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"
......
......@@ -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
......
class Essay < ActiveRecord::Base
belongs_to :writer, :primary_key => :name, :polymorphic => true
end
......@@ -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
......
......@@ -39,6 +39,7 @@ def extension_two
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
......
......@@ -161,6 +161,12 @@ def create_table(*args, &block)
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 @@ def create_table(*args, &block)
t.boolean :approved, :default => true
t.integer :replies_count, :default => 0
t.integer :parent_id
t.string :parent_title
t.string :type
end
......
......@@ -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
......@@ -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
module ActiveResource
module Observing
extend ActiveSupport::Concern
include ActiveModel::Observing
included do
wrap_with_notifications :create, :save, :update, :destroy
end
end
end
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
......@@ -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?
......
......@@ -311,8 +311,8 @@ def test_sprintf_percent_in_replacement
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 @@ def test_sprintf_old_style
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
<%= 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'
<%= shebang %>
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/console'
<%= shebang %>
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/dbconsole'
<%= shebang %>
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/destroy'
<%= shebang %>
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/generate'
<%= shebang %>
require File.dirname(__FILE__) + '/../../config/boot'
require File.expand_path('../../../config/boot', __FILE__)
require 'commands/performance/benchmarker'
<%= shebang %>
require File.dirname(__FILE__) + '/../../config/boot'
require File.expand_path('../../../config/boot', __FILE__)
require 'commands/performance/profiler'
<%= shebang %>
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/plugin'
<%= shebang %>
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/runner'
<%= shebang %>
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/server'
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
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
#!/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
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
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
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
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
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
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
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
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
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
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
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册