diff --git a/README.md b/README.md index 6c6fcdb..1e2b69b 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,10 @@ The example config above for example would use a database called "project_test". ## Generators +### Configuration + + $ rails generate couchrest_model:config + ### Model $ rails generate model person --orm=couchrest_model diff --git a/VERSION b/VERSION index 2304d76..45a1b3f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0.beta5 +1.1.2 diff --git a/couchrest_model.gemspec b/couchrest_model.gemspec index ce7f2ab..cc22676 100644 --- a/couchrest_model.gemspec +++ b/couchrest_model.gemspec @@ -6,7 +6,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version= s.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber", "Sam Lown"] - s.date = %q{2011-04-29} + s.date = File.mtime('VERSION') s.description = %q{CouchRest Model provides aditional features to the standard CouchRest Document class such as properties, view designs, associations, callbacks, typecasting and validations.} s.email = %q{jchris@apache.org} s.extra_rdoc_files = [ @@ -23,12 +23,14 @@ Gem::Specification.new do |s| s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] - s.add_dependency(%q, "1.1.0.pre2") + s.add_dependency(%q, "~> 1.1.2") s.add_dependency(%q, "~> 1.15") s.add_dependency(%q, "~> 3.0") s.add_dependency(%q, "~> 0.3.22") - s.add_development_dependency(%q, ">= 2.0.0") + s.add_development_dependency(%q, "~> 2.6.0") + s.add_development_dependency(%q, ["~> 1.5.1"]) s.add_development_dependency(%q, ">= 0.5.7") + s.add_development_dependency("rake", ">= 0.8.0") # s.add_development_dependency("jruby-openssl", ">= 0.7.3") end diff --git a/history.md b/history.md index 24d0bd3..a8b18ce 100644 --- a/history.md +++ b/history.md @@ -1,10 +1,41 @@ # CouchRest Model Change History -## 1.1.0 - 2011-05-XX +## 1.1.3 + + * CouchRest::Model::Base.respond_to_missing? and respond_to? (Kim Burgestrand) + +## 1.1.2 - 2011-07-23 + +* Minor fixes + * Upgrade to couchrest 1.1.2 + * Override as_couch_json to ensure nil values not stored + * Removing restriction that prohibited objects that cast as an array to be loaded. + +## 1.1.1 - 2011-07-04 + +* Minor fix + * Bumping CouchRest version dependency for important initialize method fix. + * Ensuring super on Embeddable#initialize can be called. + +## 1.1.0 - 2011-06-25 + +* Major Alterations + * CastedModel no longer requires a Hash. Automatically includes all required methods. + * CastedModel module renamed to Embeddable (old still works!) + +* Minor Fixes + * Validation callbacks now support context (thanks kostia) + * Document comparisons now performed using database and document ID (pointer by neocsr) + * Automatic config generation now supported (thanks lucasrenan) + * Comparing documents resorts to Hash comparison if both IDs are nil. (pointer by kostia) + +## 1.1.0.rc1 - 2011-06-08 * New Features * Properties with a nil value are now no longer sent to the database. * Now possible to build new objects via CastedArray#build + * Implement #get! and #find! class methods + * Now is possible delete particular elements in casted array(Kostiantyn Kahanskyi) * Minor fixes * #as_json now correctly uses ActiveSupports methods. @@ -14,7 +45,12 @@ * DesignDoc cache refreshed if a database is deleted. * Fixing dirty tracking on collection_of association. * Uniqueness Validation views created on initialization, not on demand! - + * #destroy freezes object instead of removing _id and _rev, better for callbacks (pointer by karmi) + * #destroyed? method now available + * #reload no longer uses Hash#merge! which was causing issues with dirty tracking on casted models. (pointer by kostia) + * Non-property mass assignment on #new no longer possible without :directly_set_attributes option. + * Using CouchRest 1.1.0.pre3. (No more Hashes!) + * Fixing problem assigning a CastedHash to a property declared as a Hash (Kostiantyn Kahanskyi, gfmtim) ## 1.1.0.beta5 - 2011-04-30 diff --git a/lib/couchrest/model/associations.rb b/lib/couchrest/model/associations.rb index f8e5a12..e98e48e 100644 --- a/lib/couchrest/model/associations.rb +++ b/lib/couchrest/model/associations.rb @@ -183,19 +183,19 @@ module CouchRest casted_by[casted_by_property.to_s] << obj.id end end - + def << obj check_obj(obj) casted_by[casted_by_property.to_s] << obj.id super(obj) end - + def push(obj) check_obj(obj) casted_by[casted_by_property.to_s].push obj.id super(obj) end - + def unshift(obj) check_obj(obj) casted_by[casted_by_property.to_s].unshift obj.id @@ -212,7 +212,7 @@ module CouchRest casted_by[casted_by_property.to_s].pop super end - + def shift casted_by[casted_by_property.to_s].shift super diff --git a/lib/couchrest/model/base.rb b/lib/couchrest/model/base.rb index 5f3a6cd..40cb968 100644 --- a/lib/couchrest/model/base.rb +++ b/lib/couchrest/model/base.rb @@ -1,13 +1,12 @@ module CouchRest module Model - class Base < Document + class Base < CouchRest::Document extend ActiveModel::Naming include CouchRest::Model::Configuration include CouchRest::Model::Connection include CouchRest::Model::Persistence - include CouchRest::Model::Callbacks include CouchRest::Model::DocumentQueries include CouchRest::Model::Views include CouchRest::Model::DesignDoc @@ -18,9 +17,11 @@ module CouchRest include CouchRest::Model::PropertyProtection include CouchRest::Model::Associations include CouchRest::Model::Validations + include CouchRest::Model::Callbacks include CouchRest::Model::Designs include CouchRest::Model::CastedBy include CouchRest::Model::Dirty + include CouchRest::Model::Callbacks def self.subclasses @subclasses ||= [] @@ -46,22 +47,24 @@ module CouchRest # # Options supported: # - # * :directly_set_attributes: true when data comes directly from database - # * :database: provide an alternative database + # * :directly_set_attributes, true when data comes directly from database + # * :database, provide an alternative database # # If a block is provided the new model will be passed into the # block so that it can be populated. - def initialize(doc = {}, options = {}) - doc = prepare_all_attributes(doc, options) - # set the instances database, if provided + def initialize(attributes = {}, options = {}) + super() + prepare_all_attributes(attributes, options) + # set the instance's database, if provided self.database = options[:database] unless options[:database].nil? - super(doc) unless self['_id'] && self['_rev'] self[self.model_type_key] = self.class.to_s end + yield self if block_given? after_initialize if respond_to?(:after_initialize) + run_callbacks(:initialize) { self } end @@ -79,18 +82,19 @@ module CouchRest super end - ## Compatibility with ActiveSupport and older frameworks - - # Hack so that CouchRest::Document, which descends from Hash, - # doesn't appear to Rails routing as a Hash of options - def is_a?(klass) - return false if klass == Hash - super + # compatbility for 1.8, it does not use respond_to_missing? + # thing is, when using it like this only, doing method(:find_by_view) + # will throw an error + def self.respond_to?(m, include_private = false) + super || respond_to_missing?(m, include_private) end - alias :kind_of? :is_a? - def persisted? - !new? + # ruby 1.9 feature + # this allows ruby to know that the method is defined using + # method_missing, and as such, method(:find_by_view) will actually + # give a Method back, and not throw an error like in 1.8! + def self.respond_to_missing?(m, include_private = false) + has_view?(m) || has_view?(m.to_s[/^find_(by_.+)/, 1]) end def to_key @@ -100,6 +104,26 @@ module CouchRest alias :to_param :id alias :new_record? :new? alias :new_document? :new? + + # Compare this model with another by confirming to see + # if the IDs and their databases match! + # + # Camparison of the database is required in case the + # model has been proxied or loaded elsewhere. + # + # A Basic CouchRest document will only ever compare using + # a Hash comparison on the attributes. + def == other + return false unless other.is_a?(Base) + if id.nil? && other.id.nil? + # no ids? assume comparing nested and revert to hash comparison + to_hash == other.to_hash + else + database == other.database && id == other.id + end + end + alias :eql? :== + end end end diff --git a/lib/couchrest/model/callbacks.rb b/lib/couchrest/model/callbacks.rb index 3037810..18a36e9 100644 --- a/lib/couchrest/model/callbacks.rb +++ b/lib/couchrest/model/callbacks.rb @@ -5,21 +5,23 @@ module CouchRest #:nodoc: module Callbacks extend ActiveSupport::Concern + + CALLBACKS = [ + :before_validation, :after_validation, + :after_initialize, + :before_create, :around_create, :after_create, + :before_destroy, :around_destroy, :after_destroy, + :before_save, :around_save, :after_save, + :before_update, :around_update, :after_update, + ] + included do extend ActiveModel::Callbacks + include ActiveModel::Validations::Callbacks - define_model_callbacks \ - :create, - :destroy, - :save, - :update - + define_model_callbacks :initialize, :only => :after + define_model_callbacks :create, :destroy, :save, :update end - - def valid?(*) #nodoc - _run_validation_callbacks { super } - end - end end diff --git a/lib/couchrest/model/casted_array.rb b/lib/couchrest/model/casted_array.rb index 8e4ff66..65b7f46 100644 --- a/lib/couchrest/model/casted_array.rb +++ b/lib/couchrest/model/casted_array.rb @@ -50,6 +50,16 @@ module CouchRest::Model super end + def delete(obj) + couchrest_parent_will_change! if use_dirty? && self.length > 0 + super(obj) + end + + def delete_at(index) + couchrest_parent_will_change! if use_dirty? && self.length > 0 + super(index) + end + def build(*args) obj = casted_by_property.build(*args) self.push(obj) diff --git a/lib/couchrest/model/collection.rb b/lib/couchrest/model/collection.rb index 540b299..5d6dd76 100644 --- a/lib/couchrest/model/collection.rb +++ b/lib/couchrest/model/collection.rb @@ -244,6 +244,7 @@ module CouchRest else options = { :limit => per_page, :skip => per_page * (page - 1) } end + options[:include_docs] = true view_options.merge(options) end diff --git a/lib/couchrest/model/document_queries.rb b/lib/couchrest/model/document_queries.rb index 839674b..22aec40 100644 --- a/lib/couchrest/model/document_queries.rb +++ b/lib/couchrest/model/document_queries.rb @@ -1,13 +1,10 @@ module CouchRest module Model module DocumentQueries - - def self.included(base) - base.extend(ClassMethods) - end - + extend ActiveSupport::Concern + module ClassMethods - + # Load all documents that have the model_type_key's field equal to the # name of the current class. Take the standard set of # CouchRest::Database#view options. @@ -73,7 +70,7 @@ module CouchRest end end alias :find :get - + # Load a document from the database by id # An exception will be raised if the document isn't found # diff --git a/lib/couchrest/model/casted_model.rb b/lib/couchrest/model/embeddable.rb similarity index 71% rename from lib/couchrest/model/casted_model.rb rename to lib/couchrest/model/embeddable.rb index f20de16..05970a2 100644 --- a/lib/couchrest/model/casted_model.rb +++ b/lib/couchrest/model/embeddable.rb @@ -1,37 +1,37 @@ module CouchRest::Model - module CastedModel - + module Embeddable extend ActiveSupport::Concern + # Include Attributes early to ensure super() will work + include CouchRest::Attributes + included do include CouchRest::Model::Configuration - include CouchRest::Model::Callbacks include CouchRest::Model::Properties include CouchRest::Model::PropertyProtection include CouchRest::Model::Associations include CouchRest::Model::Validations + include CouchRest::Model::Callbacks include CouchRest::Model::CastedBy include CouchRest::Model::Dirty + include CouchRest::Model::Callbacks + class_eval do # Override CastedBy's base_doc? def base_doc? false # Can never be base doc! end + end end - def initialize(keys = {}) - raise StandardError unless self.is_a? Hash - prepare_all_attributes(keys) + # Initialize a new Casted Model. Accepts the same + # options as CouchRest::Model::Base for preparing and initializing + # attributes. + def initialize(keys = {}, options = {}) super() - end - - def []= key, value - super(key.to_s, value) - end - - def [] key - super(key.to_s) + prepare_all_attributes(keys, options) + run_callbacks(:initialize) { self } end # False if the casted model has already @@ -65,6 +65,14 @@ module CouchRest::Model end alias :attributes= :update_attributes_without_saving + end # End Embeddable + + # Provide backwards compatability with previous versions (pre 1.1.0) + module CastedModel + extend ActiveSupport::Concern + included do + include CouchRest::Model::Embeddable + end end end diff --git a/lib/couchrest/model/persistence.rb b/lib/couchrest/model/persistence.rb index e77b663..a9e0a75 100644 --- a/lib/couchrest/model/persistence.rb +++ b/lib/couchrest/model/persistence.rb @@ -21,14 +21,15 @@ module CouchRest # Creates the document in the db. Raises an exception # if the document is not created properly. - def create! - self.class.fail_validate!(self) unless self.create + def create!(options = {}) + self.class.fail_validate!(self) unless self.create(options) end # Trigger the callbacks (before, after, around) # only if the document isn't new def update(options = {}) - raise "Calling #{self.class.name}#update on document that has not been created!" if self.new? + raise "Cannot save a destroyed document!" if destroyed? + raise "Calling #{self.class.name}#update on document that has not been created!" if new? return false unless perform_validations(options) return true if !self.disable_dirty && !self.changed? _run_update_callbacks do @@ -54,19 +55,25 @@ module CouchRest end # Deletes the document from the database. Runs the :destroy callbacks. - # Removes the _id and _rev fields, preparing the - # document to be saved to a new _id if required. def destroy _run_destroy_callbacks do result = database.delete_doc(self) if result['ok'] - self.delete('_rev') - self.delete('_id') + @_destroyed = true + self.freeze end result['ok'] end end + def destroyed? + !!@_destroyed + end + + def persisted? + !new? && !destroyed? + end + # Update the document's attributes and save. For example: # # doc.update_attributes :name => "Fred" @@ -85,7 +92,7 @@ module CouchRest # # Returns self. def reload - merge!(self.class.get(id)) + prepare_all_attributes(database.get(id), :directly_set_attributes => true) self end @@ -104,22 +111,25 @@ module CouchRest module ClassMethods - # Creates a new instance, bypassing attribute protection + # Creates a new instance, bypassing attribute protection and + # uses the type field to determine which model to use to instanatiate + # the new object. # # ==== Returns # a document instance # - def build_from_database(doc = {}) - base = (doc[model_type_key].blank? || doc[model_type_key] == self.to_s) ? self : doc[model_type_key].constantize - base.new(doc, :directly_set_attributes => true) + def build_from_database(doc = {}, options = {}, &block) + src = doc[model_type_key] + base = (src.blank? || src == self.to_s) ? self : src.constantize + base.new(doc, options.merge(:directly_set_attributes => true), &block) end # Defines an instance and save it directly to the database # # ==== Returns # returns the reloaded document - def create(attributes = {}) - instance = new(attributes) + def create(attributes = {}, &block) + instance = new(attributes, &block) instance.create instance end @@ -128,8 +138,8 @@ module CouchRest # # ==== Returns # returns the reloaded document or raises an exception - def create!(attributes = {}) - instance = new(attributes) + def create!(attributes = {}, &block) + instance = new(attributes, &block) instance.create! instance end diff --git a/lib/couchrest/model/properties.rb b/lib/couchrest/model/properties.rb index 5d815ab..54cfb71 100644 --- a/lib/couchrest/model/properties.rb +++ b/lib/couchrest/model/properties.rb @@ -12,8 +12,10 @@ module CouchRest raise "You can only mixin Properties in a class responding to [] and []=, if you tried to mixin CastedModel, make sure your class inherits from Hash or responds to the proper methods" unless (method_defined?(:[]) && method_defined?(:[]=)) end - def as_json(options = nil) - Hash[self].reject{|k,v| v.nil?}.as_json(options) + # Provide an attribute hash ready to be sent to CouchDB but with + # all the nil attributes removed. + def as_couch_json + super.delete_if{|k,v| v.nil?} end # Returns the Class properties with their values @@ -80,17 +82,18 @@ module CouchRest self.disable_dirty = dirty end - def prepare_all_attributes(doc = {}, options = {}) + def prepare_all_attributes(attrs = {}, options = {}) self.disable_dirty = !!options[:directly_set_attributes] apply_all_property_defaults if options[:directly_set_attributes] - directly_set_read_only_attributes(doc) + directly_set_read_only_attributes(attrs) + directly_set_attributes(attrs, true) else - doc = remove_protected_attributes(doc) + attrs = remove_protected_attributes(attrs) + directly_set_attributes(attrs) end - res = doc.nil? ? doc : directly_set_attributes(doc) self.disable_dirty = false - res + self end def find_property!(property) @@ -101,16 +104,13 @@ module CouchRest # Set all the attributes and return a hash with the attributes # that have not been accepted. - def directly_set_attributes(hash) - hash.reject do |attribute_name, attribute_value| - if self.respond_to?("#{attribute_name}=") - self.send("#{attribute_name}=", attribute_value) - true - elsif mass_assign_any_attribute # config option - self[attribute_name] = attribute_value - true - else - false + def directly_set_attributes(hash, mass_assign = false) + return if hash.nil? + hash.reject do |key, value| + if self.respond_to?("#{key}=") + self.send("#{key}=", value) + elsif mass_assign || mass_assign_any_attribute + self[key] = value end end end @@ -151,15 +151,13 @@ module CouchRest # These properties are casted as Time objects, so they should always # be set to UTC. def timestamps! - class_eval <<-EOS, __FILE__, __LINE__ - property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false) - property(:created_at, Time, :read_only => true, :protected => true, :auto_validation => false) + property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false) + property(:created_at, Time, :read_only => true, :protected => true, :auto_validation => false) - set_callback :save, :before do |object| - write_attribute('updated_at', Time.now) - write_attribute('created_at', Time.now) if object.new? - end - EOS + set_callback :save, :before do |object| + write_attribute('updated_at', Time.now) + write_attribute('created_at', Time.now) if object.new? + end end protected @@ -170,8 +168,8 @@ module CouchRest # check if this property is going to casted type = options.delete(:type) || options.delete(:cast_as) if block_given? - type = Class.new(Hash) do - include CastedModel + type = Class.new do + include Embeddable end if block.arity == 1 # Traditional, with options type.class_eval { yield type } @@ -193,42 +191,32 @@ module CouchRest # defines the getter for the property (and optional aliases) def create_property_getter(property) - # meth = property.name - class_eval <<-EOS, __FILE__, __LINE__ + 1 - def #{property.name} - read_attribute('#{property.name}') - end - EOS + define_method(property.name) do + read_attribute(property.name) + end if ['boolean', TrueClass.to_s.downcase].include?(property.type.to_s.downcase) - class_eval <<-EOS, __FILE__, __LINE__ - def #{property.name}? - value = read_attribute('#{property.name}') - !(value.nil? || value == false) - end - EOS + define_method("#{property.name}?") do + value = read_attribute(property.name) + !(value.nil? || value == false) + end end if property.alias - class_eval <<-EOS, __FILE__, __LINE__ + 1 - alias #{property.alias.to_sym} #{property.name.to_sym} - EOS + alias_method(property.alias, property.name.to_sym) end end # defines the setter for the property (and optional aliases) def create_property_setter(property) - property_name = property.name - class_eval <<-EOS - def #{property_name}=(value) - write_attribute('#{property_name}', value) - end - EOS + name = property.name + + define_method("#{name}=") do |value| + write_attribute(name, value) + end if property.alias - class_eval <<-EOS - alias #{property.alias.to_sym}= #{property_name.to_sym}= - EOS + alias_method "#{property.alias}=", "#{name}=" end end diff --git a/lib/couchrest/model/property.rb b/lib/couchrest/model/property.rb index 7661858..6058fda 100644 --- a/lib/couchrest/model/property.rb +++ b/lib/couchrest/model/property.rb @@ -27,19 +27,15 @@ module CouchRest::Model if value.nil? value = [] elsif [Hash, HashWithIndifferentAccess].include?(value.class) - # Assume provided as a Hash where key is index! - data = value - value = [ ] - data.keys.sort.each do |k| - value << data[k] - end + # Assume provided as a params hash where key is index + value = parameter_hash_to_array(value) elsif !value.is_a?(Array) raise "Expecting an array or keyed hash for property #{parent.class.name}##{self.name}" end arr = value.collect { |data| cast_value(parent, data) } # allow casted_by calls to be passed up chain by wrapping in CastedArray CastedArray.new(arr, self, parent) - elsif (type == Object || type == Hash) && (value.class == Hash) + elsif (type == Object || type == Hash) && (value.is_a?(Hash)) # allow casted_by calls to be passed up chain by wrapping in CastedHash CastedHash[value, self, parent] elsif !value.nil? @@ -47,9 +43,8 @@ module CouchRest::Model end end - # Cast an individual value, not an array + # Cast an individual value def cast_value(parent, value) - raise "An array inside an array cannot be casted, use CastedModel" if value.is_a?(Array) value = typecast_value(value, self) associate_casted_value_to_parent(parent, value) end @@ -78,6 +73,14 @@ module CouchRest::Model private + def parameter_hash_to_array(source) + value = [ ] + source.keys.each do |k| + value[k.to_i] = source[k] + end + value.compact + end + def associate_casted_value_to_parent(parent, value) value.casted_by = parent if value.respond_to?(:casted_by) value.casted_by_property = self if value.respond_to?(:casted_by_property) diff --git a/lib/couchrest/model/proxyable.rb b/lib/couchrest/model/proxyable.rb index 53a88da..9fb812c 100644 --- a/lib/couchrest/model/proxyable.rb +++ b/lib/couchrest/model/proxyable.rb @@ -73,12 +73,12 @@ module CouchRest end # Base - def new(*args) - proxy_update(model.new(*args)) + def new(attrs = {}, options = {}, &block) + proxy_block_update(:new, attrs, options, &block) end - def build_from_database(doc = {}) - proxy_update(model.build_from_database(doc)) + def build_from_database(attrs = {}, options = {}, &block) + proxy_block_update(:build_from_database, attrs, options, &block) end def method_missing(m, *args, &block) @@ -170,6 +170,13 @@ module CouchRest end end + def proxy_block_update(method, *args, &block) + model.send(method, *args) do |doc| + proxy_update(doc) + yield doc if block_given? + end + end + end end end diff --git a/lib/couchrest/model/support/couchrest_design.rb b/lib/couchrest/model/support/couchrest_design.rb index a5caf07..73e9eaa 100644 --- a/lib/couchrest/model/support/couchrest_design.rb +++ b/lib/couchrest/model/support/couchrest_design.rb @@ -18,7 +18,7 @@ CouchRest::Design.class_eval do flatten = lambda {|r| (recurse = lambda {|v| - if v.is_a?(Hash) + if v.is_a?(Hash) || v.is_a?(CouchRest::Document) v.to_a.map{|v| recurse.call(v)}.flatten elsif v.is_a?(Array) v.flatten.map{|v| recurse.call(v)} diff --git a/lib/couchrest/model/validations.rb b/lib/couchrest/model/validations.rb index 11a2571..825b169 100644 --- a/lib/couchrest/model/validations.rb +++ b/lib/couchrest/model/validations.rb @@ -13,22 +13,33 @@ module CouchRest # Validations may be applied to both Model::Base and Model::CastedModel module Validations extend ActiveSupport::Concern - included do - include ActiveModel::Validations - include ActiveModel::Validations::Callbacks + include ActiveModel::Validations + + # Determine if the document is valid. + # + # @example Is the document valid? + # person.valid? + # + # @example Is the document valid in a context? + # person.valid?(:create) + # + # @param [ Symbol ] context The optional validation context. + # + # @return [ true, false ] True if valid, false if not. + # + def valid?(context = nil) + super context ? context : (new? ? :create : :update) end - module ClassMethods - - # Validates the associated casted model. This method should not be + + # Validates the associated casted model. This method should not be # used within your code as it is automatically included when a CastedModel # is used inside the model. - # def validates_casted_model(*args) validates_with(CastedModelValidator, _merge_attributes(args)) end - + # Validates if the field is unique for this type of document. Automatically creates # a view if one does not already exist and performs a search for all matching # documents. diff --git a/lib/couchrest/railtie.rb b/lib/couchrest/railtie.rb index b1266c4..0c4d04a 100644 --- a/lib/couchrest/railtie.rb +++ b/lib/couchrest/railtie.rb @@ -2,10 +2,13 @@ require "rails" require "active_model/railtie" module CouchRest - # = Active Record Railtie class ModelRailtie < Rails::Railtie - config.generators.orm :couchrest_model - config.generators.test_framework :test_unit, :fixture => false + def self.generator + config.respond_to?(:app_generators) ? :app_generators : :generators + end + + config.send(generator).orm :couchrest_model + config.send(generator).test_framework :test_unit, :fixture => false initializer "couchrest_model.configure_default_connection" do CouchRest::Model::Base.configure do |conf| diff --git a/lib/couchrest_model.rb b/lib/couchrest_model.rb index 956a2a6..699da9e 100644 --- a/lib/couchrest_model.rb +++ b/lib/couchrest_model.rb @@ -33,7 +33,6 @@ require "couchrest/model/property_protection" require "couchrest/model/properties" require "couchrest/model/casted_array" require "couchrest/model/casted_hash" -require "couchrest/model/casted_model" require "couchrest/model/validations" require "couchrest/model/callbacks" require "couchrest/model/document_queries" @@ -58,10 +57,10 @@ require "couchrest/model/core_extensions/hash" require "couchrest/model/core_extensions/time_parsing" # Base libraries -require "couchrest/model/casted_model" +require "couchrest/model/embeddable" require "couchrest/model/base" -# Add rails support *after* everything has loaded +# Add rails support *after* everything has loaded if defined?(Rails) require "couchrest/railtie" end diff --git a/lib/rails/generators/couchrest_model/config/config_generator.rb b/lib/rails/generators/couchrest_model/config/config_generator.rb new file mode 100644 index 0000000..b78b4d5 --- /dev/null +++ b/lib/rails/generators/couchrest_model/config/config_generator.rb @@ -0,0 +1,18 @@ +require 'rails/generators/couchrest_model' + +module CouchrestModel + module Generators + class ConfigGenerator < Rails::Generators::Base + source_root File.expand_path('../templates', __FILE__) + + def app_name + Rails::Application.subclasses.first.parent.to_s.underscore + end + + def copy_configuration_file + template 'couchdb.yml', File.join('config', "couchdb.yml") + end + + end + end +end \ No newline at end of file diff --git a/lib/rails/generators/couchrest_model/config/templates/couchdb.yml b/lib/rails/generators/couchrest_model/config/templates/couchdb.yml new file mode 100644 index 0000000..2216a90 --- /dev/null +++ b/lib/rails/generators/couchrest_model/config/templates/couchdb.yml @@ -0,0 +1,21 @@ +development: &development + protocol: 'http' + host: localhost + port: 5984 + prefix: <%= app_name %> + suffix: development + username: + password: + +test: + <<: *development + suffix: test + +production: + protocol: 'https' + host: localhost + port: 5984 + prefix: <%= app_name %> + suffix: production + username: root + password: 123 \ No newline at end of file diff --git a/spec/couchrest/inherited_spec.rb b/spec/couchrest/inherited_spec.rb deleted file mode 100644 index 69eba6a..0000000 --- a/spec/couchrest/inherited_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -require File.expand_path('../../spec_helper', __FILE__) - -begin - require 'rubygems' unless ENV['SKIP_RUBYGEMS'] - require 'active_support/json' - ActiveSupport::JSON.backend = :JSONGem - - class PlainParent - class_inheritable_accessor :foo - self.foo = :bar - end - - class PlainChild < PlainParent - end - - class ExtendedParent < CouchRest::Model::Base - class_inheritable_accessor :foo - self.foo = :bar - end - - class ExtendedChild < ExtendedParent - end - - describe "Using chained inheritance without CouchRest::Model::Base" do - it "should preserve inheritable attributes" do - PlainParent.foo.should == :bar - PlainChild.foo.should == :bar - end - end - - describe "Using chained inheritance with CouchRest::Model::Base" do - it "should preserve inheritable attributes" do - ExtendedParent.foo.should == :bar - ExtendedChild.foo.should == :bar - end - end - -rescue LoadError - puts "This spec requires 'active_support/json' to be loaded" -end diff --git a/spec/fixtures/more/article.rb b/spec/fixtures/models/article.rb similarity index 97% rename from spec/fixtures/more/article.rb rename to spec/fixtures/models/article.rb index 9e370e3..6e2af99 100644 --- a/spec/fixtures/more/article.rb +++ b/spec/fixtures/models/article.rb @@ -22,6 +22,7 @@ class Article < CouchRest::Model::Base property :date, Date property :slug, :read_only => true + property :user_id property :title property :tags, [String] diff --git a/spec/fixtures/base.rb b/spec/fixtures/models/base.rb similarity index 86% rename from spec/fixtures/base.rb rename to spec/fixtures/models/base.rb index d395b4a..8e62eb8 100644 --- a/spec/fixtures/base.rb +++ b/spec/fixtures/models/base.rb @@ -83,18 +83,32 @@ class WithCallBacks < CouchRest::Model::Base end end +# Following two fixture classes have __intentionally__ diffent syntax for setting the validation context +class WithContextualValidationOnCreate < CouchRest::Model::Base + use_database TEST_SERVER.default_database + property(:name, String) + validates(:name, :presence => {:on => :create}) +end + +class WithContextualValidationOnUpdate < CouchRest::Model::Base + use_database TEST_SERVER.default_database + property(:name, String) + validates(:name, :presence => true, :on => :update) +end + class WithTemplateAndUniqueID < CouchRest::Model::Base use_database TEST_SERVER.default_database unique_id do |model| - model['important-field'] + model.slug end + property :slug property :preset, :default => 'value' property :has_no_default end class WithGetterAndSetterMethods < CouchRest::Model::Base use_database TEST_SERVER.default_database - + property :other_arg def arg other_arg @@ -107,7 +121,7 @@ end class WithAfterInitializeMethod < CouchRest::Model::Base use_database TEST_SERVER.default_database - + property :some_value def after_initialize diff --git a/spec/fixtures/more/card.rb b/spec/fixtures/models/card.rb similarity index 95% rename from spec/fixtures/more/card.rb rename to spec/fixtures/models/card.rb index 7494b44..8cf72ab 100644 --- a/spec/fixtures/more/card.rb +++ b/spec/fixtures/models/card.rb @@ -1,3 +1,5 @@ +require 'person' + class Card < CouchRest::Model::Base # Set the default database to use use_database DB diff --git a/spec/fixtures/more/cat.rb b/spec/fixtures/models/cat.rb similarity index 71% rename from spec/fixtures/more/cat.rb rename to spec/fixtures/models/cat.rb index 903eafd..a28f505 100644 --- a/spec/fixtures/more/cat.rb +++ b/spec/fixtures/models/cat.rb @@ -1,6 +1,6 @@ -class CatToy < Hash - include ::CouchRest::Model::CastedModel +class CatToy + include CouchRest::Model::Embeddable property :name @@ -17,3 +17,7 @@ class Cat < CouchRest::Model::Base property :number end +class ChildCat < Cat + property :mother, Cat + property :siblings, [Cat] +end diff --git a/spec/fixtures/more/client.rb b/spec/fixtures/models/client.rb similarity index 100% rename from spec/fixtures/more/client.rb rename to spec/fixtures/models/client.rb diff --git a/spec/fixtures/more/course.rb b/spec/fixtures/models/course.rb similarity index 88% rename from spec/fixtures/more/course.rb rename to spec/fixtures/models/course.rb index d06f6a0..2998bb2 100644 --- a/spec/fixtures/more/course.rb +++ b/spec/fixtures/models/course.rb @@ -1,5 +1,5 @@ -require File.join(FIXTURE_PATH, 'more', 'question') -require File.join(FIXTURE_PATH, 'more', 'person') +require 'question' +require 'person' class Course < CouchRest::Model::Base use_database TEST_SERVER.default_database diff --git a/spec/fixtures/more/event.rb b/spec/fixtures/models/event.rb similarity index 100% rename from spec/fixtures/more/event.rb rename to spec/fixtures/models/event.rb diff --git a/spec/fixtures/more/invoice.rb b/spec/fixtures/models/invoice.rb similarity index 98% rename from spec/fixtures/more/invoice.rb rename to spec/fixtures/models/invoice.rb index 540c9ff..927fd8f 100644 --- a/spec/fixtures/more/invoice.rb +++ b/spec/fixtures/models/invoice.rb @@ -6,9 +6,9 @@ class Invoice < CouchRest::Model::Base property :client_name property :employee_name property :location - + # Validation validates_presence_of :client_name, :employee_name validates_presence_of :location, :message => "Hey stupid!, you forgot the location" - + end diff --git a/spec/fixtures/models/key_chain.rb b/spec/fixtures/models/key_chain.rb new file mode 100644 index 0000000..0837e39 --- /dev/null +++ b/spec/fixtures/models/key_chain.rb @@ -0,0 +1,5 @@ +class KeyChain < CouchRest::Model::Base + use_database(DB) + + property(:keys, Hash) +end diff --git a/spec/fixtures/models/membership.rb b/spec/fixtures/models/membership.rb new file mode 100644 index 0000000..81c804e --- /dev/null +++ b/spec/fixtures/models/membership.rb @@ -0,0 +1,4 @@ +class Membership + include CouchRest::Model::Embeddable + +end diff --git a/spec/fixtures/more/person.rb b/spec/fixtures/models/person.rb similarity index 56% rename from spec/fixtures/more/person.rb rename to spec/fixtures/models/person.rb index 076bbc0..71525e4 100644 --- a/spec/fixtures/more/person.rb +++ b/spec/fixtures/models/person.rb @@ -1,5 +1,7 @@ -class Person < Hash - include ::CouchRest::Model::CastedModel +require 'cat' + +class Person + include ::CouchRest::Model::Embeddable property :pet, Cat property :name, [String] diff --git a/spec/fixtures/models/project.rb b/spec/fixtures/models/project.rb new file mode 100644 index 0000000..d8de244 --- /dev/null +++ b/spec/fixtures/models/project.rb @@ -0,0 +1,6 @@ +class Project < CouchRest::Model::Base + use_database DB + property :name, String + timestamps! + view_by :name +end diff --git a/spec/fixtures/models/question.rb b/spec/fixtures/models/question.rb new file mode 100644 index 0000000..ada5bd9 --- /dev/null +++ b/spec/fixtures/models/question.rb @@ -0,0 +1,7 @@ +class Question + include ::CouchRest::Model::Embeddable + + property :q + property :a + +end diff --git a/spec/fixtures/more/sale_entry.rb b/spec/fixtures/models/sale_entry.rb similarity index 100% rename from spec/fixtures/more/sale_entry.rb rename to spec/fixtures/models/sale_entry.rb diff --git a/spec/fixtures/more/sale_invoice.rb b/spec/fixtures/models/sale_invoice.rb similarity index 60% rename from spec/fixtures/more/sale_invoice.rb rename to spec/fixtures/models/sale_invoice.rb index ad6beb2..5244ba5 100644 --- a/spec/fixtures/more/sale_invoice.rb +++ b/spec/fixtures/models/sale_invoice.rb @@ -1,6 +1,7 @@ -require File.join(FIXTURE_PATH, 'more', 'client') -require File.join(FIXTURE_PATH, 'more', 'sale_entry') -class SaleInvoice < CouchRest::Model::Base +require 'client' +require 'sale_entry' + +class SaleInvoice < CouchRest::Model::Base use_database DB belongs_to :client @@ -10,4 +11,4 @@ class SaleInvoice < CouchRest::Model::Base property :date, Date property :price, Integer -end \ No newline at end of file +end diff --git a/spec/fixtures/more/service.rb b/spec/fixtures/models/service.rb similarity index 100% rename from spec/fixtures/more/service.rb rename to spec/fixtures/models/service.rb diff --git a/spec/fixtures/more/user.rb b/spec/fixtures/models/user.rb similarity index 100% rename from spec/fixtures/more/user.rb rename to spec/fixtures/models/user.rb diff --git a/spec/fixtures/more/question.rb b/spec/fixtures/more/question.rb deleted file mode 100644 index 5efcd20..0000000 --- a/spec/fixtures/more/question.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Question < Hash - include ::CouchRest::Model::CastedModel - - property :q - property :a - -end diff --git a/spec/functional/validations_spec.rb b/spec/functional/validations_spec.rb new file mode 100644 index 0000000..fc06203 --- /dev/null +++ b/spec/functional/validations_spec.rb @@ -0,0 +1,8 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe CouchRest::Model::Validations do + + let(:invoice) do + Invoice.new() + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 75ed89d..d04b99f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,11 +1,16 @@ +$LOAD_PATH.unshift(File.dirname(__FILE__)) +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) + require "bundler/setup" require "rubygems" -require "rspec" # Satisfies Autotest and anyone else not using the Rake tasks +require "rspec" -require File.join(File.dirname(__FILE__), '..','lib','couchrest_model') -# check the following file to see how to use the spec'd features. +require 'couchrest_model' unless defined?(FIXTURE_PATH) + MODEL_PATH = File.join(File.dirname(__FILE__), "fixtures", "models") + $LOAD_PATH.unshift(MODEL_PATH) + FIXTURE_PATH = File.join(File.dirname(__FILE__), '/fixtures') SCRATCH_PATH = File.join(File.dirname(__FILE__), '/tmp') @@ -16,6 +21,21 @@ unless defined?(FIXTURE_PATH) DB = TEST_SERVER.database(TESTDB) end +RSpec.configure do |config| + config.before(:all) { reset_test_db! } + + config.after(:all) do + cr = TEST_SERVER + test_dbs = cr.databases.select { |db| db =~ /^#{TESTDB}/ } + test_dbs.each do |db| + cr.database(db).delete! rescue nil + end + end +end + +# Require each of the fixture models +Dir[ File.join(MODEL_PATH, "*.rb") ].sort.each { |file| require File.basename(file) } + class Basic < CouchRest::Model::Base use_database TEST_SERVER.default_database end @@ -27,17 +47,6 @@ def reset_test_db! DB end -RSpec.configure do |config| - config.before(:all) { reset_test_db! } - - config.after(:all) do - cr = TEST_SERVER - test_dbs = cr.databases.select { |db| db =~ /^#{TESTDB}/ } - test_dbs.each do |db| - cr.database(db).delete! rescue nil - end - end -end def couchdb_lucene_available? lucene_path = "http://localhost:5985/" diff --git a/spec/unit/active_model_lint_spec.rb b/spec/unit/active_model_lint_spec.rb new file mode 100644 index 0000000..3cc5d3d --- /dev/null +++ b/spec/unit/active_model_lint_spec.rb @@ -0,0 +1,30 @@ +# encoding: utf-8 +require 'spec_helper' +require 'test/unit/assertions' +require 'active_model/lint' + +class CompliantModel < CouchRest::Model::Base +end + + +describe CouchRest::Model::Base do + include Test::Unit::Assertions + include ActiveModel::Lint::Tests + + before :each do + @model = CompliantModel.new + end + + describe "active model lint tests" do + ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m| + example m.gsub('_',' ') do + send m + end + end + end + + def model + @model + end + +end diff --git a/spec/couchrest/assocations_spec.rb b/spec/unit/assocations_spec.rb similarity index 98% rename from spec/couchrest/assocations_spec.rb rename to spec/unit/assocations_spec.rb index 0affb6e..528b5d0 100644 --- a/spec/couchrest/assocations_spec.rb +++ b/spec/unit/assocations_spec.rb @@ -1,7 +1,5 @@ # encoding: utf-8 -require File.expand_path('../../spec_helper', __FILE__) -require File.join(FIXTURE_PATH, 'more', 'sale_invoice') - +require 'spec_helper' describe "Assocations" do diff --git a/spec/couchrest/attachment_spec.rb b/spec/unit/attachment_spec.rb similarity index 99% rename from spec/couchrest/attachment_spec.rb rename to spec/unit/attachment_spec.rb index 420b88c..0d050d3 100644 --- a/spec/couchrest/attachment_spec.rb +++ b/spec/unit/attachment_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../spec_helper', __FILE__) +require 'spec_helper' describe "Model attachments" do diff --git a/spec/couchrest/base_spec.rb b/spec/unit/base_spec.rb similarity index 78% rename from spec/couchrest/base_spec.rb rename to spec/unit/base_spec.rb index 208d6e7..de4a619 100644 --- a/spec/couchrest/base_spec.rb +++ b/spec/unit/base_spec.rb @@ -1,11 +1,5 @@ # encoding: utf-8 - -require File.expand_path("../../spec_helper", __FILE__) -require File.join(FIXTURE_PATH, 'more', 'cat') -require File.join(FIXTURE_PATH, 'more', 'article') -require File.join(FIXTURE_PATH, 'more', 'course') -require File.join(FIXTURE_PATH, 'more', 'card') -require File.join(FIXTURE_PATH, 'base') +require "spec_helper" describe "Model Base" do @@ -49,8 +43,45 @@ describe "Model Base" do @obj.database.should eql('database') end + it "should only set defined properties" do + @doc = WithDefaultValues.new(:name => 'test', :foo => 'bar') + @doc['name'].should eql('test') + @doc['foo'].should be_nil + end + + it "should set all properties with :directly_set_attributes option" do + @doc = WithDefaultValues.new({:name => 'test', :foo => 'bar'}, :directly_set_attributes => true) + @doc['name'].should eql('test') + @doc['foo'].should eql('bar') + end + + it "should set the model type" do + @doc = WithDefaultValues.new() + @doc[WithDefaultValues.model_type_key].should eql('WithDefaultValues') + end + + it "should call after_initialize method if available" do + @doc = WithAfterInitializeMethod.new + @doc['some_value'].should eql('value') + end + + it "should call after_initialize after block" do + @doc = WithAfterInitializeMethod.new {|d| d.some_value = "foo"} + @doc['some_value'].should eql('foo') + end + + it "should call after_initialize callback if available" do + klass = Class.new(CouchRest::Model::Base) + klass.class_eval do # for ruby 1.8.7 + property :name + after_initialize :set_name + def set_name; self.name = "foobar"; end + end + @doc = klass.new + @doc.name.should eql("foobar") + end end - + describe "ActiveModel compatability Basic" do before(:each) do @@ -90,14 +121,22 @@ describe "Model Base" do describe "#persisted?" do context "when the document is new" do it "returns false" do - @obj.persisted?.should == false + @obj.persisted?.should be_false end end context "when the document is not new" do it "returns id" do @obj.save - @obj.persisted?.should == true + @obj.persisted?.should be_true + end + end + + context "when the document is destroyed" do + it "returns false" do + @obj.save + @obj.destroy + @obj.persisted?.should be_false end end end @@ -109,9 +148,59 @@ describe "Model Base" do end end - + describe "#destroyed?" do + it "should be present" do + @obj.should respond_to(:destroyed?) + end + it "should return false with new object" do + @obj.destroyed?.should be_false + end + it "should return true after destroy" do + @obj.save + @obj.destroy + @obj.destroyed?.should be_true + end + end end - + + describe "comparisons" do + describe "#==" do + context "on saved document" do + it "should be true on same document" do + p = Project.create + p.should eql(p) + end + it "should be true after loading" do + p = Project.create + p.should eql(Project.get(p.id)) + end + it "should not be true if databases do not match" do + p = Project.create + p2 = p.dup + p2.stub!(:database).and_return('other') + p.should_not eql(p2) + end + it "should always be false if one document not saved" do + p = Project.create(:name => 'test') + o = Project.new(:name => 'test') + p.should_not eql(o) + end + end + context "with new documents" do + it "should be true when attributes match" do + p = Project.new(:name => 'test') + o = Project.new(:name => 'test') + p.should eql(o) + end + it "should not be true when attributes don't match" do + p = Project.new(:name => 'test') + o = Project.new(:name => 'testing') + p.should_not eql(o) + end + end + end + end + describe "update attributes without saving" do before(:each) do a = Article.get "big-bad-danger" rescue nil @@ -152,7 +241,7 @@ describe "Model Base" do }.should_not raise_error @art.slug.should == "big-bad-danger" end - + #it "should not change other attributes if there is an error" do # lambda { # @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger") @@ -160,7 +249,7 @@ describe "Model Base" do # @art['title'].should == "big bad danger" #end end - + describe "update attributes" do before(:each) do a = Article.get "big-bad-danger" rescue nil @@ -175,7 +264,7 @@ describe "Model Base" do loaded['title'].should == "super danger" end end - + describe "with default" do it "should have the default value set at initalization" do @obj.preset.should == {:right => 10, :top_align => false} @@ -232,7 +321,7 @@ describe "Model Base" do WithTemplateAndUniqueID.all.map{|o| o.destroy} WithTemplateAndUniqueID.database.bulk_delete @tmpl = WithTemplateAndUniqueID.new - @tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1') + @tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'slug' => '1') end it "should have fields set when new" do @tmpl.preset.should == 'value' @@ -253,10 +342,10 @@ describe "Model Base" do before(:all) do WithTemplateAndUniqueID.all.map{|o| o.destroy} WithTemplateAndUniqueID.database.bulk_delete - WithTemplateAndUniqueID.new('important-field' => '1').save - WithTemplateAndUniqueID.new('important-field' => '2').save - WithTemplateAndUniqueID.new('important-field' => '3').save - WithTemplateAndUniqueID.new('important-field' => '4').save + WithTemplateAndUniqueID.new('slug' => '1').save + WithTemplateAndUniqueID.new('slug' => '2').save + WithTemplateAndUniqueID.new('slug' => '3').save + WithTemplateAndUniqueID.new('slug' => '4').save end it "should find all" do rs = WithTemplateAndUniqueID.all @@ -274,9 +363,9 @@ describe "Model Base" do end it ".count should return the number of documents" do - WithTemplateAndUniqueID.new('important-field' => '1').save - WithTemplateAndUniqueID.new('important-field' => '2').save - WithTemplateAndUniqueID.new('important-field' => '3').save + WithTemplateAndUniqueID.new('slug' => '1').save + WithTemplateAndUniqueID.new('slug' => '2').save + WithTemplateAndUniqueID.new('slug' => '3').save WithTemplateAndUniqueID.count.should == 3 end @@ -285,14 +374,14 @@ describe "Model Base" do describe "finding the first instance of a model" do before(:each) do @db = reset_test_db! - WithTemplateAndUniqueID.new('important-field' => '1').save - WithTemplateAndUniqueID.new('important-field' => '2').save - WithTemplateAndUniqueID.new('important-field' => '3').save - WithTemplateAndUniqueID.new('important-field' => '4').save + WithTemplateAndUniqueID.new('slug' => '1').save + WithTemplateAndUniqueID.new('slug' => '2').save + WithTemplateAndUniqueID.new('slug' => '3').save + WithTemplateAndUniqueID.new('slug' => '4').save end it "should find first" do rs = WithTemplateAndUniqueID.first - rs['important-field'].should == "1" + rs['slug'].should == "1" end it "should return nil if no instances are found" do WithTemplateAndUniqueID.all.each {|obj| obj.destroy } @@ -370,14 +459,7 @@ describe "Model Base" do end end - describe "initialization" do - it "should call after_initialize method if available" do - @doc = WithAfterInitializeMethod.new - @doc['some_value'].should eql('value') - end - end - - describe "recursive validation on a model" do + describe "recursive validation on a model" do before :each do reset_test_db! @cat = Cat.new(:name => 'Sockington') diff --git a/spec/couchrest/casted_spec.rb b/spec/unit/casted_spec.rb similarity index 89% rename from spec/couchrest/casted_spec.rb rename to spec/unit/casted_spec.rb index 44e4231..a7b4860 100644 --- a/spec/couchrest/casted_spec.rb +++ b/spec/unit/casted_spec.rb @@ -1,7 +1,4 @@ -require File.expand_path('../../spec_helper', __FILE__) -require File.join(FIXTURE_PATH, 'more', 'cat') -require File.join(FIXTURE_PATH, 'more', 'person') -require File.join(FIXTURE_PATH, 'more', 'card') +require "spec_helper" class Driver < CouchRest::Model::Base use_database TEST_SERVER.default_database diff --git a/spec/couchrest/class_proxy_spec.rb b/spec/unit/class_proxy_spec.rb similarity index 98% rename from spec/couchrest/class_proxy_spec.rb rename to spec/unit/class_proxy_spec.rb index 1572d8d..f945d30 100644 --- a/spec/couchrest/class_proxy_spec.rb +++ b/spec/unit/class_proxy_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path("../../spec_helper", __FILE__) +require "spec_helper" class UnattachedDoc < CouchRest::Model::Base # Note: no use_database here diff --git a/spec/couchrest/collection_spec.rb b/spec/unit/collection_spec.rb similarity index 92% rename from spec/couchrest/collection_spec.rb rename to spec/unit/collection_spec.rb index 528a350..e501072 100644 --- a/spec/couchrest/collection_spec.rb +++ b/spec/unit/collection_spec.rb @@ -1,5 +1,4 @@ -require File.expand_path("../../spec_helper", __FILE__) -require File.join(FIXTURE_PATH, 'more', 'article') +require "spec_helper" describe "Collections" do @@ -27,21 +26,20 @@ describe "Collections" do end it "should provide a class method for paginate" do articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date', - :per_page => 3, :descending => true, :key => Date.today, :include_docs => true) + :per_page => 3, :descending => true, :key => Date.today) articles.size.should == 3 - + articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date', - :per_page => 3, :page => 2, :descending => true, :key => Date.today, :include_docs => true) + :per_page => 3, :page => 2, :descending => true, :key => Date.today) articles.size.should == 3 - + articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date', - :per_page => 3, :page => 3, :descending => true, :key => Date.today, :include_docs => true) + :per_page => 3, :page => 3, :descending => true, :key => Date.today) articles.size.should == 1 end it "should provide a class method for paginated_each" do options = { :design_doc => 'Article', :view_name => 'by_date', - :per_page => 3, :page => 1, :descending => true, :key => Date.today, - :include_docs => true } + :per_page => 3, :page => 1, :descending => true, :key => Date.today } Article.paginated_each(options) do |a| a.should_not be_nil end diff --git a/spec/couchrest/configuration_spec.rb b/spec/unit/configuration_spec.rb similarity index 92% rename from spec/couchrest/configuration_spec.rb rename to spec/unit/configuration_spec.rb index 9053eab..26255f9 100644 --- a/spec/couchrest/configuration_spec.rb +++ b/spec/unit/configuration_spec.rb @@ -1,8 +1,7 @@ # encoding: utf-8 -require File.expand_path('../../spec_helper', __FILE__) -require File.join(FIXTURE_PATH, 'more', 'cat') +require "spec_helper" -describe CouchRest::Model::Base do +describe CouchRest::Model::Configuration do before do @class = Class.new(CouchRest::Model::Base) diff --git a/spec/couchrest/connection_spec.rb b/spec/unit/connection_spec.rb similarity index 97% rename from spec/couchrest/connection_spec.rb rename to spec/unit/connection_spec.rb index 4fc6b74..4607991 100644 --- a/spec/couchrest/connection_spec.rb +++ b/spec/unit/connection_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 -require File.expand_path('../../spec_helper', __FILE__) +require 'spec_helper' -describe CouchRest::Model::Base do +describe CouchRest::Model::Connection do before do @class = Class.new(CouchRest::Model::Base) diff --git a/spec/couchrest/core_extensions/time_parsing.rb b/spec/unit/core_extensions/time_parsing.rb similarity index 100% rename from spec/couchrest/core_extensions/time_parsing.rb rename to spec/unit/core_extensions/time_parsing.rb diff --git a/spec/couchrest/design_doc_spec.rb b/spec/unit/design_doc_spec.rb similarity index 96% rename from spec/couchrest/design_doc_spec.rb rename to spec/unit/design_doc_spec.rb index 5be2ddf..299f5e6 100644 --- a/spec/couchrest/design_doc_spec.rb +++ b/spec/unit/design_doc_spec.rb @@ -1,10 +1,7 @@ # encoding: utf-8 +require 'spec_helper' -require File.expand_path("../../spec_helper", __FILE__) -require File.join(FIXTURE_PATH, 'base') -require File.join(FIXTURE_PATH, 'more', 'article') - -describe "Design Documents" do +describe CouchRest::Model::DesignDoc do before :all do reset_test_db! @@ -231,7 +228,7 @@ describe "Design Documents" do describe "lazily refreshing the design document" do before(:all) do @db = reset_test_db! - WithTemplateAndUniqueID.new('important-field' => '1').save + WithTemplateAndUniqueID.new('slug' => '1').save end it "should not save the design doc twice" do WithTemplateAndUniqueID.all diff --git a/spec/couchrest/designs/view_spec.rb b/spec/unit/designs/view_spec.rb similarity index 100% rename from spec/couchrest/designs/view_spec.rb rename to spec/unit/designs/view_spec.rb diff --git a/spec/couchrest/designs_spec.rb b/spec/unit/designs_spec.rb similarity index 97% rename from spec/couchrest/designs_spec.rb rename to spec/unit/designs_spec.rb index 3864b69..96d02a3 100644 --- a/spec/couchrest/designs_spec.rb +++ b/spec/unit/designs_spec.rb @@ -1,10 +1,9 @@ -require File.expand_path("../../spec_helper", __FILE__) +require "spec_helper" class DesignModel < CouchRest::Model::Base - end -describe "Design" do +describe CouchRest::Model::Designs do it "should accessable from model" do DesignModel.respond_to?(:design).should be_true diff --git a/spec/couchrest/dirty_spec.rb b/spec/unit/dirty_spec.rb similarity index 89% rename from spec/couchrest/dirty_spec.rb rename to spec/unit/dirty_spec.rb index 12c4f18..ceadb16 100644 --- a/spec/couchrest/dirty_spec.rb +++ b/spec/unit/dirty_spec.rb @@ -1,12 +1,6 @@ -require File.expand_path("../../spec_helper", __FILE__) +require "spec_helper" -require File.join(FIXTURE_PATH, 'more', 'cat') -require File.join(FIXTURE_PATH, 'more', 'article') -require File.join(FIXTURE_PATH, 'more', 'course') -require File.join(FIXTURE_PATH, 'more', 'card') -require File.join(FIXTURE_PATH, 'base') - -class WithCastedModelMixin < Hash +class WithCastedModelMixin include CouchRest::Model::CastedModel property :name property :details, Object, :default => {} @@ -241,6 +235,14 @@ describe "Dirty" do end end + it "should report changes if an array is popped after reload" do + should_change_array do |array, obj| + obj.reload + obj.keywords.pop + end + end + + it "should report no changes if an empty array is popped" do should_not_change_array do |array, obj| array.clear @@ -249,6 +251,50 @@ describe "Dirty" do end end + it "should report changes on deletion from an array" do + should_change_array do |array, obj| + array << "keyword" + obj.save! + array.delete_at(0) + end + + should_change_array do |array, obj| + array << "keyword" + obj.save! + array.delete("keyword") + end + end + + it "should report changes on deletion from an array after reload" do + should_change_array do |array, obj| + array << "keyword" + obj.save! + obj.reload + array.delete_at(0) + end + + should_change_array do |array, obj| + array << "keyword" + obj.save! + obj.reload + array.delete("keyword") + end + end + + it "should report no changes on deletion from an empty array" do + should_not_change_array do |array, obj| + array.clear + obj.save! + array.delete_at(0) + end + + should_not_change_array do |array, obj| + array.clear + obj.save! + array.delete("keyword") + end + end + it "should report changes if an array is pushed" do should_change_array do |array, obj| array.push("keyword") diff --git a/spec/couchrest/casted_model_spec.rb b/spec/unit/embeddable_spec.rb similarity index 88% rename from spec/couchrest/casted_model_spec.rb rename to spec/unit/embeddable_spec.rb index 2bed08c..9b5b52e 100644 --- a/spec/couchrest/casted_model_spec.rb +++ b/spec/unit/embeddable_spec.rb @@ -1,26 +1,25 @@ # encoding: utf-8 +require "spec_helper" -require File.expand_path('../../spec_helper', __FILE__) -require File.join(FIXTURE_PATH, 'more', 'cat') -require File.join(FIXTURE_PATH, 'more', 'person') -require File.join(FIXTURE_PATH, 'more', 'card') -require File.join(FIXTURE_PATH, 'more', 'question') -require File.join(FIXTURE_PATH, 'more', 'course') - - -class WithCastedModelMixin < Hash - include CouchRest::Model::CastedModel +class WithCastedModelMixin + include CouchRest::Model::Embeddable property :name property :no_value property :details, Object, :default => {} property :casted_attribute, WithCastedModelMixin end +class OldFashionedMixin < Hash + include CouchRest::Model::CastedModel + property :name +end + class DummyModel < CouchRest::Model::Base use_database TEST_SERVER.default_database raise "Default DB not set" if TEST_SERVER.default_database.nil? property :casted_attribute, WithCastedModelMixin property :keywords, [String] + property :old_casted_attribute, OldFashionedMixin property :sub_models do |child| child.property :title end @@ -29,8 +28,8 @@ class DummyModel < CouchRest::Model::Base end end -class WithCastedCallBackModel < Hash - include CouchRest::Model::CastedModel +class WithCastedCallBackModel + include CouchRest::Model::Embeddable property :name property :run_before_validation property :run_after_validation @@ -51,19 +50,7 @@ class CastedCallbackDoc < CouchRest::Model::Base property :callback_model, WithCastedCallBackModel end -describe CouchRest::Model::CastedModel do - - describe "A non hash class including CastedModel" do - it "should fail raising and include error" do - lambda do - class NotAHashButWithCastedModelMixin - include CouchRest::CastedModel - property :name - end - - end.should raise_error - end - end +describe CouchRest::Model::Embeddable do describe "isolated" do before(:each) do @@ -82,7 +69,27 @@ describe CouchRest::Model::CastedModel do it "should always return base_doc? as false" do @obj.base_doc?.should be_false end - + it "should call after_initialize callback if available" do + klass = Class.new do + include CouchRest::Model::CastedModel + after_initialize :set_name + property :name + def set_name; self.name = "foobar"; end + end + @obj = klass.new + @obj.name.should eql("foobar") + end + it "should allow override of initialize with super" do + klass = Class.new do + include CouchRest::Model::Embeddable + after_initialize :set_name + property :name + def set_name; self.name = "foobar"; end + def initialize(attrs = {}); super(); end + end + @obj = klass.new + @obj.name.should eql("foobar") + end end describe "casted as an attribute, but without a value" do @@ -162,6 +169,33 @@ describe CouchRest::Model::CastedModel do end end + # Basic testing for an old fashioned casted hash + describe "old hash casted as attribute" do + before :each do + @obj = DummyModel.new(:old_casted_attribute => {:name => 'Testing'}) + @casted_obj = @obj.old_casted_attribute + end + it "should be available from its parent" do + @casted_obj.should be_an_instance_of(OldFashionedMixin) + end + + it "should have the getters defined" do + @casted_obj.name.should == 'Testing' + end + + it "should know who casted it" do + @casted_obj.casted_by.should == @obj + end + + it "should know which property casted it" do + @casted_obj.casted_by_property.should == @obj.properties.detect{|p| p.to_s == 'old_casted_attribute'} + end + + it "should return nil for the unknown attribute" do + @casted_obj["unknown"].should be_nil + end + end + describe "casted as an array of a different type" do before(:each) do @obj = DummyModel.new(:keywords => ['couch', 'sofa', 'relax', 'canapé']) diff --git a/spec/unit/inherited_spec.rb b/spec/unit/inherited_spec.rb new file mode 100644 index 0000000..04b8e3d --- /dev/null +++ b/spec/unit/inherited_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +class PlainParent + class_inheritable_accessor :foo + self.foo = :bar +end + +class PlainChild < PlainParent +end + +class ExtendedParent < CouchRest::Model::Base + class_inheritable_accessor :foo + self.foo = :bar +end + +class ExtendedChild < ExtendedParent +end + +describe "Using chained inheritance without CouchRest::Model::Base" do + it "should preserve inheritable attributes" do + PlainParent.foo.should == :bar + PlainChild.foo.should == :bar + end +end + +describe "Using chained inheritance with CouchRest::Model::Base" do + it "should preserve inheritable attributes" do + ExtendedParent.foo.should == :bar + ExtendedChild.foo.should == :bar + end +end + + diff --git a/spec/couchrest/persistence_spec.rb b/spec/unit/persistence_spec.rb similarity index 83% rename from spec/couchrest/persistence_spec.rb rename to spec/unit/persistence_spec.rb index 86ded62..e8f78ac 100644 --- a/spec/couchrest/persistence_spec.rb +++ b/spec/unit/persistence_spec.rb @@ -1,12 +1,7 @@ # encoding: utf-8 -require File.expand_path('../../spec_helper', __FILE__) -require File.join(FIXTURE_PATH, 'base') -require File.join(FIXTURE_PATH, 'more', 'cat') -require File.join(FIXTURE_PATH, 'more', 'article') -require File.join(FIXTURE_PATH, 'more', 'course') -require File.join(FIXTURE_PATH, 'more', 'card') +require 'spec_helper' -describe "Model Persistence" do +describe CouchRest::Model::Persistence do before(:each) do @obj = WithDefaultValues.new @@ -34,11 +29,11 @@ describe "Model Persistence" do describe "basic saving and retrieving" do it "should work fine" do @obj.name = "should be easily saved and retrieved" - @obj.save - saved_obj = WithDefaultValues.get(@obj.id) + @obj.save! + saved_obj = WithDefaultValues.get!(@obj.id) saved_obj.should_not be_nil end - + it "should parse the Time attributes automatically" do @obj.name = "should parse the Time attributes automatically" @obj.set_by_proc.should be_an_instance_of(Time) @@ -81,6 +76,18 @@ describe "Model Persistence" do article.should_not be_new end + it "yields new instance to block before saving (#create)" do + article = Article.create{|a| a.title = 'my create init block test'} + article.title.should == 'my create init block test' + article.should_not be_new + end + + it "yields new instance to block before saving (#create!)" do + article = Article.create{|a| a.title = 'my create bang init block test'} + article.title.should == 'my create bang init block test' + article.should_not be_new + end + it "should trigger the create callbacks" do doc = WithCallBacks.create(:name => 'my other test') doc.run_before_create.should be_true @@ -210,57 +217,66 @@ describe "Model Persistence" do it "should require the field" do lambda{@templated.save}.should raise_error - @templated['important-field'] = 'very-important' + @templated['slug'] = 'very-important' @templated.save.should be_true end it "should save with the id" do - @templated['important-field'] = 'very-important' + @templated['slug'] = 'very-important' @templated.save.should be_true t = WithTemplateAndUniqueID.get('very-important') t.should == @templated end it "should not change the id on update" do - @templated['important-field'] = 'very-important' + @templated['slug'] = 'very-important' @templated.save.should be_true - @templated['important-field'] = 'not-important' + @templated['slug'] = 'not-important' @templated.save.should be_true t = WithTemplateAndUniqueID.get('very-important') t.id.should == @templated.id end it "should raise an error when the id is taken" do - @templated['important-field'] = 'very-important' + @templated['slug'] = 'very-important' @templated.save.should be_true - lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error + lambda{WithTemplateAndUniqueID.new('slug' => 'very-important').save}.should raise_error end it "should set the id" do - @templated['important-field'] = 'very-important' + @templated['slug'] = 'very-important' @templated.save.should be_true @templated.id.should == 'very-important' end end - + describe "destroying an instance" do before(:each) do - @dobj = Basic.new + @dobj = Event.new @dobj.save.should be_true end it "should return true" do result = @dobj.destroy result.should be_true end - it "should be resavable" do - @dobj.destroy - @dobj.rev.should be_nil - @dobj.id.should be_nil - @dobj.save.should be_true - end it "should make it go away" do @dobj.destroy - lambda{Basic.get!(@dobj.id)}.should raise_error + lambda{Basic.get!(@dobj.id)}.should raise_error(CouchRest::Model::DocumentNotFound) + end + it "should freeze the object" do + @dobj.destroy + # In Ruby 1.9.2 this raises RuntimeError, in 1.8.7 TypeError, D'OH! + lambda { @dobj.subject = "Test" }.should raise_error(StandardError) + end + it "trying to save after should fail" do + @dobj.destroy + lambda { @dobj.save }.should raise_error(StandardError) + lambda{Basic.get!(@dobj.id)}.should raise_error(CouchRest::Model::DocumentNotFound) + end + it "should make destroyed? true" do + @dobj.destroyed?.should be_false + @dobj.destroy + @dobj.destroyed?.should be_true end end @@ -340,6 +356,27 @@ describe "Model Persistence" do end end + describe "with contextual validation on ”create”" do + it "should validate only within ”create” context" do + doc = WithContextualValidationOnCreate.new + doc.save.should be_false + doc.name = "Alice" + doc.save.should be_true + + doc.update_attributes(:name => nil).should be_true + end + end + + describe "with contextual validation on ”update”" do + it "should validate only within ”update” context" do + doc = WithContextualValidationOnUpdate.new + doc.save.should be_true + + doc.update_attributes(:name => nil).should be_false + doc.update_attributes(:name => "Bob").should be_true + end + end + describe "save" do it "should run the after filter after saving" do @doc.run_after_save.should be_nil diff --git a/spec/couchrest/property_protection_spec.rb b/spec/unit/property_protection_spec.rb similarity index 99% rename from spec/couchrest/property_protection_spec.rb rename to spec/unit/property_protection_spec.rb index eb5ca55..9de0d94 100644 --- a/spec/couchrest/property_protection_spec.rb +++ b/spec/unit/property_protection_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path("../../spec_helper", __FILE__) +require "spec_helper" describe "Model Attributes" do diff --git a/spec/couchrest/property_spec.rb b/spec/unit/property_spec.rb similarity index 89% rename from spec/couchrest/property_spec.rb rename to spec/unit/property_spec.rb index bb55bfc..ce4bd89 100644 --- a/spec/couchrest/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -1,17 +1,7 @@ # encoding: utf-8 -require File.expand_path('../../spec_helper', __FILE__) -require File.join(FIXTURE_PATH, 'more', 'article') -require File.join(FIXTURE_PATH, 'more', 'cat') -require File.join(FIXTURE_PATH, 'more', 'person') -require File.join(FIXTURE_PATH, 'more', 'card') -require File.join(FIXTURE_PATH, 'more', 'invoice') -require File.join(FIXTURE_PATH, 'more', 'service') -require File.join(FIXTURE_PATH, 'more', 'event') -require File.join(FIXTURE_PATH, 'more', 'user') -require File.join(FIXTURE_PATH, 'more', 'course') +require 'spec_helper' - -describe "Model properties" do +describe CouchRest::Model::Property do before(:each) do reset_test_db! @@ -72,15 +62,23 @@ describe "Model properties" do @card.updated_at.should_not be_nil end - describe "#as_json" do + describe "#as_couch_json" do it "should provide a simple hash from model" do - @card.as_json.class.should eql(Hash) + @card.as_couch_json.class.should eql(Hash) end it "should remove properties from Hash if value is nil" do @card.last_name = nil - @card.as_json.keys.include?('last_name').should be_false + @card.as_couch_json.keys.include?('last_name').should be_false + end + + end + + describe "#as_json" do + + it "should provide a simple hash from model" do + @card.as_json.class.should eql(Hash) end it "should pass options to Active Support's as_json" do @@ -239,6 +237,16 @@ describe "Model properties" do end +describe "properties of hash of casted models" do + it "should be able to assign a casted hash to a hash property" do + chain = KeyChain.new + keys = {"House" => "8==$", "Office" => "<>==U"} + chain.keys = keys + chain.keys = chain.keys + chain.keys.should == keys + end +end + describe "properties of array of casted models" do before(:each) do @@ -265,9 +273,9 @@ describe "properties of array of casted models" do end it "should allow attribute to be set from hash with ordered keys and sub-hashes" do - @course.questions = { '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} } - @course.questions.length.should eql(2) - @course.questions.last.q.should eql('Test2') + @course.questions = { '10' => {:q => 'Test10'}, '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} } + @course.questions.length.should eql(3) + @course.questions.last.q.should eql('Test10') @course.questions.last.class.should eql(Question) end @@ -284,7 +292,7 @@ describe "properties of array of casted models" do it "should raise an error if attempting to set single value for array type" do lambda { @course.questions = Question.new(:q => 'test1') - }.should raise_error + }.should raise_error(/Expecting an array/) end @@ -315,6 +323,28 @@ describe "a casted model retrieved from the database" do end end +describe "nested models (not casted)" do + before(:each) do + reset_test_db! + @cat = ChildCat.new(:name => 'Stimpy') + @cat.mother = {:name => 'Stinky'} + @cat.siblings = [{:name => 'Feather'}, {:name => 'Felix'}] + @cat.save + @cat = ChildCat.get(@cat.id) + end + + it "should correctly save single relation" do + @cat.mother.name.should eql('Stinky') + @cat.mother.casted_by.should eql(@cat) + end + + it "should correctly save collection" do + @cat.siblings.first.name.should eql("Feather") + @cat.siblings.last.casted_by.should eql(@cat) + end + +end + describe "Property Class" do it "should provide name as string" do @@ -420,15 +450,18 @@ describe "Property Class" do ary.last.should eql(Date.new(2011, 05, 22)) end - it "should raise and error if value is array when type is not" do - property = CouchRest::Model::Property.new(:test, Date) + it "should cast an object that provides an array" do + prop = Class.new do + attr_accessor :ary + def initialize(val); self.ary = val; end + def as_json; ary; end + end + property = CouchRest::Model::Property.new(:test, prop) parent = mock("FooClass") - lambda { - cast = property.cast(parent, [Date.new(2010, 6, 1)]) - }.should raise_error + cast = property.cast(parent, [1, 2]) + cast.ary.should eql([1, 2]) end - it "should set parent as casted_by object in CastedArray" do property = CouchRest::Model::Property.new(:test, [Object]) parent = mock("FooObject") diff --git a/spec/couchrest/proxyable_spec.rb b/spec/unit/proxyable_spec.rb similarity index 94% rename from spec/couchrest/proxyable_spec.rb rename to spec/unit/proxyable_spec.rb index 7e0c508..fd38fbc 100644 --- a/spec/couchrest/proxyable_spec.rb +++ b/spec/unit/proxyable_spec.rb @@ -1,6 +1,4 @@ -require File.expand_path("../../spec_helper", __FILE__) - -require File.join(FIXTURE_PATH, 'more', 'cat') +require "spec_helper" class DummyProxyable < CouchRest::Model::Base proxy_database_method :db @@ -12,7 +10,7 @@ end class ProxyKitten < CouchRest::Model::Base end -describe "Proxyable" do +describe CouchRest::Model::Proxyable do describe "#proxy_database" do @@ -87,7 +85,7 @@ describe "Proxyable" do DummyProxyable.proxy_for(:cats) @obj = DummyProxyable.new CouchRest::Model::Proxyable::ModelProxy.should_receive(:new).with(Cat, @obj, 'dummy_proxyable', 'db').and_return(true) - @obj.should_receive('proxy_database').and_return('db') + @obj.should_receive(:proxy_database).and_return('db') @obj.cats end @@ -165,15 +163,13 @@ describe "Proxyable" do end it "should proxy new call" do - Cat.should_receive(:new).and_return({}) - @obj.should_receive(:proxy_update).and_return(true) - @obj.new + @obj.should_receive(:proxy_block_update).with(:new, 'attrs', 'opts') + @obj.new('attrs', 'opts') end it "should proxy build_from_database" do - Cat.should_receive(:build_from_database).and_return({}) - @obj.should_receive(:proxy_update).with({}).and_return(true) - @obj.build_from_database + @obj.should_receive(:proxy_block_update).with(:build_from_database, 'attrs', 'opts') + @obj.build_from_database('attrs', 'opts') end describe "#method_missing" do @@ -313,6 +309,15 @@ describe "Proxyable" do @obj.send(:proxy_update_all, docs) end + describe "#proxy_block_update" do + it "should proxy block updates" do + doc = { } + @obj.model.should_receive(:new).and_yield(doc) + @obj.should_receive(:proxy_update).with(doc) + @obj.send(:proxy_block_update, :new) + end + end + end end diff --git a/spec/couchrest/subclass_spec.rb b/spec/unit/subclass_spec.rb similarity index 89% rename from spec/couchrest/subclass_spec.rb rename to spec/unit/subclass_spec.rb index 75ab218..d8f835e 100644 --- a/spec/couchrest/subclass_spec.rb +++ b/spec/unit/subclass_spec.rb @@ -1,8 +1,4 @@ -require File.expand_path("../../spec_helper", __FILE__) -require File.join(FIXTURE_PATH, 'more', 'cat') -require File.join(FIXTURE_PATH, 'more', 'person') -require File.join(FIXTURE_PATH, 'more', 'card') -require File.join(FIXTURE_PATH, 'more', 'course') +require "spec_helper" # add a default value Card.property :bg_color, :default => '#ccc' diff --git a/spec/couchrest/typecast_spec.rb b/spec/unit/typecast_spec.rb similarity index 98% rename from spec/couchrest/typecast_spec.rb rename to spec/unit/typecast_spec.rb index 4174001..6db127e 100644 --- a/spec/couchrest/typecast_spec.rb +++ b/spec/unit/typecast_spec.rb @@ -1,8 +1,5 @@ # encoding: utf-8 -require File.expand_path('../../spec_helper', __FILE__) -require File.join(FIXTURE_PATH, 'more', 'cat') -require File.join(FIXTURE_PATH, 'more', 'person') -require File.join(FIXTURE_PATH, 'more', 'course') +require 'spec_helper' describe "Type Casting" do diff --git a/spec/couchrest/validations_spec.rb b/spec/unit/validations_spec.rb similarity index 92% rename from spec/couchrest/validations_spec.rb rename to spec/unit/validations_spec.rb index b387323..b9e2b09 100644 --- a/spec/couchrest/validations_spec.rb +++ b/spec/unit/validations_spec.rb @@ -1,14 +1,6 @@ -require File.expand_path("../../spec_helper", __FILE__) +require "spec_helper" -require File.join(FIXTURE_PATH, 'more', 'cat') -require File.join(FIXTURE_PATH, 'more', 'article') -require File.join(FIXTURE_PATH, 'more', 'course') -require File.join(FIXTURE_PATH, 'more', 'card') -require File.join(FIXTURE_PATH, 'base') - -# TODO Move validations from other specs to here - -describe "Validations" do +describe CouchRest::Model::Validations do describe "Uniqueness" do diff --git a/spec/couchrest/view_spec.rb b/spec/unit/view_spec.rb similarity index 95% rename from spec/couchrest/view_spec.rb rename to spec/unit/view_spec.rb index d54d252..d69630c 100644 --- a/spec/couchrest/view_spec.rb +++ b/spec/unit/view_spec.rb @@ -1,10 +1,6 @@ -require File.expand_path("../../spec_helper", __FILE__) -require File.join(FIXTURE_PATH, 'more', 'cat') -require File.join(FIXTURE_PATH, 'more', 'person') -require File.join(FIXTURE_PATH, 'more', 'article') -require File.join(FIXTURE_PATH, 'more', 'course') +require "spec_helper" -describe "Model views" do +describe CouchRest::Model::Views do class Unattached < CouchRest::Model::Base property :title @@ -17,7 +13,6 @@ describe "Model views" do nil end end - describe "ClassMethods" do # NOTE! Add more unit tests! @@ -178,7 +173,22 @@ describe "Model views" do end end - + + describe "#method_missing for find_by methods" do + before(:all) { reset_test_db! } + + specify { Course.should respond_to :find_by_title_and_active } + specify { Course.should respond_to :by_title } + + specify "#method should work in ruby 1.9, but not 1.8" do + if RUBY_VERSION >= "1.9" + Course.method(:find_by_title_and_active).should be_a Method + else + expect { Course.method(:find_by_title_and_active) }.to raise_error(NameError) + end + end + end + describe "a ducktype view" do before(:all) do reset_test_db!