module RSpec::Rails::Mocks

def mock_model(string_or_model_class, stubs = {})

* A Class that extends ActiveModel::Naming
* A String representing a Class that extends ActiveModel::Naming
* A String representing a Class that does not exist

+string_or_model_class+ can be any of:

new_record?).
ActiveModel API (which declares persisted?, not
extension frameworks that have yet to update themselves to the
persisted?, and is present only for compatibility with
stubbed out implicitly. new_record? returns the inverse of
NOTE that only ActiveModel's methods, plus new_record?, are

impersonating models that don't exist yet.
stubbed (via add_stubs) if +stubs+ is passed. This is most useful for
ActiveModel methods stubbed out. Additional methods may be easily
Creates a test double representing +string_or_model_class+ with common
def mock_model(string_or_model_class, stubs = {})
  if String === string_or_model_class
    if Object.const_defined?(string_or_model_class)
      model_class = Object.const_get(string_or_model_class)
    else
      model_class = Object.const_set(string_or_model_class, Class.new do
        extend ActiveModel::Naming
      end)
    end
  else
    model_class = string_or_model_class
  end
  unless model_class.kind_of? ActiveModel::Naming
    raise ArgumentError.new <<-EOM
ck_model method can only accept as its first argument:
String representing a Class that does not exist
String representing a Class that extends ActiveModel::Naming
Class that extends ActiveModel::Naming
eived #{model_class.inspect}
  end
  stubs = stubs.reverse_merge(:id => next_id)
  stubs = stubs.reverse_merge(:persisted? => !!stubs[:id])
  stubs = stubs.reverse_merge(:destroyed? => false)
  stubs = stubs.reverse_merge(:marked_for_destruction? => false)
  stubs = stubs.reverse_merge(:blank? => false)
  mock("#{model_class.name}_#{stubs[:id]}", stubs).tap do |m|
    m.extend ActiveModelInstanceMethods
    m.singleton_class.__send__ :include, ActiveModel::Conversion
    m.singleton_class.__send__ :include, ActiveModel::Validations
    if defined?(ActiveRecord)
      m.extend ActiveRecordInstanceMethods
      [:save, :update_attributes].each do |key|
        if stubs[key] == false
          m.errors.stub(:empty?) { false }
        end
      end
    end
    m.__send__(:__mock_proxy).instance_eval(<<-CODE, __FILE__, __LINE__)
      def @object.is_a?(other)
        #{model_class}.ancestors.include?(other)
      end
      def @object.kind_of?(other)
        #{model_class}.ancestors.include?(other)
      end
      def @object.instance_of?(other)
        other == #{model_class}
      end
      def @object.respond_to?(method_name, include_private=false)
        #{model_class}.respond_to?(:column_names) && #{model_class}.column_names.include?(method_name.to_s) || super
      end
      def @object.class
        #{model_class}
      end
      def @object.to_s
        "#{model_class.name}_#{to_param}"
      end
    CODE
    yield m if block_given?
  end
end

def next_id

def next_id
  @@model_id += 1
end

def stub_model(model_class, stubs={})

end
person.first_name = "David"
stub_model(Person) do |person|
stub_model(Person, :to_param => 37)
stub_model(Person).as_new_record
stub_model(Person)

== Examples

inherently more state-based than interaction-based.
helper), it is especially useful in view examples, which are
While you can use stub_model in any example (model, view, controller,

example a bit more descriptive.
case persisted? will return false, but using +as_new_record+ makes the
set the id to nil. You can also explicitly set :id => nil, in which
the object to behave as a new record, sending it +as_new_record+ will
This means that by default persisted? will return true. If you want
persisted? is overridden to return the result of !id.nil?

framework.
assigned as a stub return value using RSpec's mocking/stubbing
the model does not have a matching attribute, the key/value pair is
(determined by asking it) are simply assigned the submitted values. If
For each key in +hash_of_stubs+, if the model has a matching attribute

+ActiveRecord+ model, it is prohibited from accessing the database*.
generated value that is unique to each object.. If +Model+ is an
Creates an instance of +Model+ with +to_param+ stubbed using a

stub_model(Model, instance_variable_name, hash_of_stubs)
stub_model(Model, hash_of_stubs)
stub_model(Model).as_new_record
stub_model(Model)
:call-seq:
def stub_model(model_class, stubs={})
  model_class.new.tap do |m|
    m.extend ActiveModelStubExtensions
    if defined?(ActiveRecord) && model_class < ActiveRecord::Base
      m.extend ActiveRecordStubExtensions
      primary_key = model_class.primary_key.to_sym
      stubs = stubs.reverse_merge(primary_key => next_id)
      stubs = stubs.reverse_merge(:persisted? => !!stubs[primary_key])
    else
      stubs = stubs.reverse_merge(:id => next_id)
      stubs = stubs.reverse_merge(:persisted? => !!stubs[:id])
    end
    stubs = stubs.reverse_merge(:blank? => false)
    stubs.each do |k,v|
      m.__send__("#{k}=", stubs.delete(k)) if m.respond_to?("#{k}=")
    end
    m.stub(stubs)
    yield m if block_given?
  end
end