lib/test_dummy.rb in test_dummy-0.2.10 vs lib/test_dummy.rb in test_dummy-0.3.0

- old
+ new

@@ -2,19 +2,31 @@ if (defined?(Rails)) # Only load the Railtie if Rails is loaded. require 'test_dummy/railtie' end + class Exception < ::Exception + end + autoload(:Helper, File.expand_path('test_dummy/helper', File.dirname(__FILE__))) autoload(:TestHelper, File.expand_path('test_dummy/test_helper', File.dirname(__FILE__))) - def self.dummy_directory - @dummy_directory or (defined?(Rails) and Rails.root) + # Returns the current path used to load dummy extensions into models, or + # nil if no path is currently defined. Defaults to "test/dummy" off of the + # Rails root if Rails is available. + def self.dummy_extensions_path + @dummy_extensions_path ||= begin + if (defined?(Rails)) + File.expand_path('test/dummy', Rails.root) + else + nil + end + end end - def self.dummy_directory=(value) - @dummy_directory = value + def self.dummy_extensions_path=(value) + @dummy_extensions_path = value end def self.included(base) base.send(:extend, ClassMethods) base.send(:include, InstanceMethods) @@ -43,38 +55,66 @@ end end combined_attributes end + + # This method is used to provide a unified interface to the otherwise + # irregular methods to discover information on assocations. Rails 3 + # introduces a new method. Returns the reflected class and foreign key + # properties for a named attribute, or nil if no association could be found. + def self.reflection_properties(model_class, attribute) + if (model_class.respond_to?(:reflect_on_association) and reflection = model_class.reflect_on_association(attribute)) + [ + reflection.klass, + (reflection.respond_to?(:foreign_key) ? reflection.foreign_key : reflection.primary_key_name).to_sym + ] + elsif (model_class.respond_to?(:association_reflection) and reflection = model_class.association_reflection(attribute)) + [ + reflection[:associated_class], + reflection[:key] || :"#{attribute.to_s.underscore}_id" + ] + end + end end # Adds a mixin to the core Helper module def self.add_module(new_module) Helper.send(:extend, new_module) end + + # Used to configure defaults or aliases that can be used by all definitions. + # Takes a block that should call definition methods like `dummy`. + def self.define(&block) + instance_eval(&block) + end # Used in an initializer to define things that can be dummied by all # models if these properties are available. def self.dummy(*names, &block) case (names.last) when Hash options = names.pop end if (options and options[:with]) - block = options[:with] - end + with = options[:with] - # Create a temporary Module and use this to roll up the methods defined - # into the Helper module - Helper.send( - :extend, - names.inject(Module.new) do |m, name| - m.send(:define_method, name, &block) - m + names.each do |name| + if (Helper.respond_to?(with)) + Helper.send(:alias_method, name, with) + else + Helper.send(:define_method, name) do + send(with) + end + end end - ) + else + names.each do |name| + Helper.send(:define_method, name, &block) + end + end end # Used in an initializer to define configuration parameters. def self.config(&block) TestDummy.instance_eval(&block) @@ -97,20 +137,108 @@ case (names.last) when Hash options = names.pop end - if (options and options[:with]) - block = options[:with] - end - @test_dummy ||= { } @test_dummy_order ||= [ ] names.flatten.each do |name| name = name.to_sym + from = nil + create_options_proc = nil + if (options) + if (options[:with]) + if (block) + raise TestDummy::Exception, "Cannot use block and :with option at the same time." + end + + block = + case (with = options[:with]) + when Proc + with + when String, Symbol + lambda { send(with) } + else + lambda { with } + end + end + + # The :inherit directive is used to pass arguments through to the + # create_dummy call on the association's class. + if (inherit = options[:inherit]) + inherit_options = Hash[ + inherit.collect do |attribute, spec| + [ + attribute.to_sym, + case (spec) + when Array + spec.collect(&:to_sym) + when String + spec.split('.').collect(&:to_sym) + when Proc + spec + end + ] + end + ] + + create_options_proc = lambda do |target, model, with_attributes| + inherit_options.each do |attribute, spec| + target[attribute] ||= + case (spec) + when Array + spec.inject(model) do |_model, _method| + _model ? _model.send(_method) : nil + end + when Proc + proc.call(model, with_attributes) + end + end + end + end + + if (from = options[:from]) + if (block) + raise TestDummy::Exception, "Cannot use block, :with, or :from option at the same time." + end + + case (from) + when Array + # Already in correct form + when Hash + from = from.to_a + when String + from = from.split('.') + else + raise TestDummy::Exception, "Argument to :from must be a String, Array or Hash." + end + end + + block = lambda do |model, with_attributes| + reflection_class, foreign_key = TestDummy::Support.reflection_properties(self, name) + + if (reflection_class and foreign_key) + unless ((with_attributes and (with_attributes.key?(name) or with_attributes.key?(foreign_key))) or model.send(name).present?) + object = from && from.inject(model) do |_model, _method| + _model ? _model.send(_method) : nil + end + + object ||= + reflection_class.create_dummy(with_attributes) do |target| + if (create_options_proc) + create_options_proc.call(target, model, with_attributes) + end + end + + model.send(:"#{name}=", object) + end + end + end + end + # For associations, delay creation of block until first call # to allow for additional relationships to be defined after # the to_dummy call. Leave placeholder (true) instead. @test_dummy[name] = block || true @@ -134,11 +262,11 @@ # been saved. def build_dummy(with_attributes = nil) load_dummy_declaration! build_scope = (method(:scoped).arity == 1) ? scoped(nil).scope(:create) : scoped.scope_for_create - + model = new(TestDummy::Support.combine_attributes(build_scope, with_attributes)) yield(model) if (block_given?) self.execute_dummy_operation(model, with_attributes) @@ -208,14 +336,22 @@ load_dummy_declaration! return model unless (@test_dummy_order) @test_dummy_order.each do |name| - if (reflection = reflect_on_association(name)) - unless ((with_attributes and with_attributes.key?(name.to_sym)) or model.send(name).present?) + if (respond_to?(:reflect_on_association) and reflection = reflect_on_association(name)) + foreign_key = (reflection.respond_to?(:foreign_key) ? reflection.foreign_key : reflection.primary_key_name).to_sym + + unless ((with_attributes and (with_attributes.key?(name.to_sym) or with_attributes.key?(foreign_key.to_sym))) or model.send(name).present?) model.send(:"#{name}=", dummy_method_call(model, with_attributes, dummy_method(name))) end + elsif (respond_to?(:association_reflection) and reflection = association_reflection(name)) + key = reflection[:key] || :"#{name.to_s.underscore}_id" + + unless ((with_attributes and (with_attributes.key?(name.to_sym) or with_attributes.key?(key.to_sym))) or model.send(name).present?) + model.send(:"#{name}=", dummy_method_call(model, with_attributes, dummy_method(name))) + end else unless (with_attributes and (with_attributes.key?(name.to_sym) or with_attributes.key?(name.to_s))) model.send(:"#{name}=", dummy_method_call(model, with_attributes, dummy_method(name))) end end @@ -228,19 +364,17 @@ def load_dummy_declaration! return unless (@_dummy_module.nil?) @_dummy_module = begin - dummy_path = File.expand_path( - "test/dummy/#{name.underscore}.rb", - TestDummy.dummy_directory - ) + dummy_path = File.expand_path("#{name.underscore}.rb", TestDummy.dummy_extensions_path) if (File.exist?(dummy_path)) - load dummy_path + load(dummy_path) end rescue LoadError + # Persist that this load attempt failed and don't retry later. false end end def dummy_method_call(model, with_attributes, block) @@ -263,17 +397,28 @@ when Module block.method(name) when Symbol Helper.method(name) when true - # Configure association dummyr the first time it is called - if (reflection = reflect_on_association(name)) + # Configure association dummy the first time it is called + if (respond_to?(:reflect_on_association) and reflection = reflect_on_association(name)) foreign_key = (reflection.respond_to?(:foreign_key) ? reflection.foreign_key : reflection.primary_key_name).to_sym @test_dummy[name] = lambda do |model, with_attributes| - (with_attributes and with_attributes.key?(foreign_key)) ? nil : reflection.klass.send(:create_dummy) + (with_attributes and (with_attributes.key?(foreign_key) or with_attributes.key?(name))) ? nil : reflection.klass.send(:create_dummy) end + elsif (respond_to?(:association_reflection) and reflection = association_reflection(name)) + key = reflection[:key] || :"#{name.to_s.underscore}_id" + + @test_dummy[name] = + lambda do |model, with_attributes| + (with_attributes and (with_attributes.key?(key) or with_attributes.key?(name))) ? nil : reflection[:associated_class].send(:create_dummy) + end + elsif (TestDummy::Helper.respond_to?(name)) + @test_dummy[name] = lambda do |model, with_attributes| + TestDummy::Helper.send(name) + end else raise "Cannot dummy unknown relationship #{name}" end else block