diff --git a/history.txt b/history.txt index 36254c8..d5fd15a 100644 --- a/history.txt +++ b/history.txt @@ -4,6 +4,15 @@ * Minor enhancements +== 1.0.3 + +* Minor enhancements + * Removed Validation by default, requires too many structure changes (FAIL) + * Added support for instantiation of documents read from database as couchrest-type provided (Sam Lown) + * Improved attachment handling for detecting file type (Sam Lown) + * Removing some monkey patches and relying on active_support for constantize and humanize (Sam Lown) + + == 1.0.2 * Minor enhancements @@ -14,6 +23,9 @@ * Major enhancements * Separated ExtendedDocument from main CouchRest gem (Sam Lown) +* Minor enhancements + * active_support included by default + == 0.37 * Minor enhancements diff --git a/lib/couchrest/extended_document.rb b/lib/couchrest/extended_document.rb index aac9b42..fe882fe 100644 --- a/lib/couchrest/extended_document.rb +++ b/lib/couchrest/extended_document.rb @@ -8,7 +8,7 @@ module CouchRest # Same as CouchRest::Document but with properties and validations class ExtendedDocument < Document - VERSION = "1.0.2" + VERSION = "1.0.3" include CouchRest::Mixins::Callbacks include CouchRest::Mixins::DocumentQueries @@ -19,7 +19,8 @@ module CouchRest include CouchRest::Mixins::Collection include CouchRest::Mixins::AttributeProtection - include CouchRest::Validation + # Including validation here does not work due to the way inheritance is handled. + #include CouchRest::Validation def self.subclasses @subclasses ||= [] @@ -48,17 +49,19 @@ module CouchRest # Creates a new instance, bypassing attribute protection # + # # ==== Returns # a document instance - def self.create_from_database(passed_keys={}) - new(passed_keys, :directly_set_attributes => true) + def self.create_from_database(doc = {}) + base = (doc['couchrest-type'].blank? || doc['couchrest-type'] == self.to_s) ? self : doc['couchrest-type'].constantize + base.new(doc, :directly_set_attributes => true) end - def initialize(passed_keys={}, options={}) + def initialize(doc = {}, options = {}) apply_defaults # defined in CouchRest::Mixins::Properties - remove_protected_attributes(passed_keys) unless options[:directly_set_attributes] - directly_set_attributes(passed_keys) unless passed_keys.nil? - super(passed_keys) + remove_protected_attributes(doc) unless options[:directly_set_attributes] + directly_set_attributes(doc) unless doc.nil? + super(doc) cast_keys # defined in CouchRest::Mixins::Properties unless self['_id'] && self['_rev'] self['couchrest-type'] = self.class.to_s diff --git a/lib/couchrest/mixins/extended_attachments.rb b/lib/couchrest/mixins/extended_attachments.rb index 5b5e2b8..970f678 100644 --- a/lib/couchrest/mixins/extended_attachments.rb +++ b/lib/couchrest/mixins/extended_attachments.rb @@ -2,7 +2,8 @@ module CouchRest module Mixins module ExtendedAttachments - # creates a file attachment to the current doc + # Add a file attachment to the current document. Expects + # :file and :name to be included in the arguments. def create_attachment(args={}) raise ArgumentError unless args[:file] && args[:name] return if has_attachment?(args[:name]) @@ -52,13 +53,14 @@ module CouchRest private - def get_mime_type(file) - ::MIME::Types.type_for(file.path).empty? ? - 'text\/plain' : MIME::Types.type_for(file.path).first.content_type.gsub(/\//,'\/') + def get_mime_type(path) + type = ::MIME::Types.type_for(path) + type.empty? ? nil : type.first.content_type end def set_attachment_attr(args) - content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file]) + content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file].path) + content_type ||= (get_mime_type(args[:name]) || 'text/plain') self['_attachments'][args[:name]] = { 'content_type' => content_type, 'data' => args[:file].read diff --git a/lib/couchrest/property.rb b/lib/couchrest/property.rb index dfd5a16..0e2b1a1 100644 --- a/lib/couchrest/property.rb +++ b/lib/couchrest/property.rb @@ -26,7 +26,7 @@ module CouchRest base_type = TrueClass else begin - base_type = ::CouchRest.constantize(base_type) + base_type = base_type.constantize rescue # leave base type as a string and convert in more/typecast end end diff --git a/lib/couchrest/support/couchrest.rb b/lib/couchrest/support/couchrest.rb index a0a89e3..e795556 100644 --- a/lib/couchrest/support/couchrest.rb +++ b/lib/couchrest/support/couchrest.rb @@ -1,43 +1,6 @@ module CouchRest - - # The CouchRest module methods handle the basic JSON serialization - # and deserialization, as well as query parameters. The module also includes - # some helpers for tasks like instantiating a new Database or Server instance. - class << self - - # extracted from Extlib - # - # Constantize tries to find a declared constant with the name specified - # in the string. It raises a NameError when the name is not in CamelCase - # or is not initialized. - # - # @example - # "Module".constantize #=> Module - # "Class".constantize #=> Class - def constantize(camel_cased_word) - unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word - raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!" - end - - Object.module_eval("::#{$1}", __FILE__, __LINE__) - end - - # extracted from Extlib - # - # Capitalizes the first word and turns underscores into spaces and strips _id. - # Like titleize, this is meant for creating pretty output. - # - # @example - # "employee_salary" #=> "Employee salary" - # "author_id" #=> "Author" - def humanize(lower_case_and_underscored_word) - lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize - end - - end - class Database alias :delete_old! :delete! diff --git a/lib/couchrest/typecast.rb b/lib/couchrest/typecast.rb index aada42e..0e8097f 100644 --- a/lib/couchrest/typecast.rb +++ b/lib/couchrest/typecast.rb @@ -28,7 +28,7 @@ module CouchRest def typecast_value(value, klass, init_method) return nil if value.nil? - klass = ::CouchRest.constantize(klass) unless klass.is_a?(Class) + klass = klass.constantize unless klass.is_a?(Class) if value.instance_of?(klass) || klass == Object value elsif [String, TrueClass, Integer, Float, BigDecimal, DateTime, Time, Date, Class].include?(klass) @@ -164,7 +164,7 @@ module CouchRest # Typecast a value to a Class def typecast_to_class(value) - ::CouchRest.constantize(value.to_s) + value.to_s.constantize rescue NameError value end diff --git a/lib/couchrest/validation/validation_errors.rb b/lib/couchrest/validation/validation_errors.rb index 7b7da0b..a1ce372 100644 --- a/lib/couchrest/validation/validation_errors.rb +++ b/lib/couchrest/validation/validation_errors.rb @@ -60,7 +60,7 @@ module CouchRest cattr_writer :default_error_messages def self.default_error_message(key, field, *values) - field = CouchRest.humanize(field) + field = field.to_s.humanize @@default_error_messages[key] % [field, *values].flatten end diff --git a/lib/couchrest/validation/validators/format_validator.rb b/lib/couchrest/validation/validators/format_validator.rb index fa61212..f15c7eb 100644 --- a/lib/couchrest/validation/validators/format_validator.rb +++ b/lib/couchrest/validation/validators/format_validator.rb @@ -64,7 +64,7 @@ module CouchRest error_message = @options[:message] || ValidationErrors.default_error_message(:invalid, field_name) - field = CouchRest.humanize(field_name) + field = field_name.to_s.humanize error_message = error_message.call(field, value) if error_message.respond_to?(:call) add_error(target, error_message, field_name) diff --git a/lib/couchrest/validation/validators/length_validator.rb b/lib/couchrest/validation/validators/length_validator.rb index ec80dff..04c3ce0 100644 --- a/lib/couchrest/validation/validators/length_validator.rb +++ b/lib/couchrest/validation/validators/length_validator.rb @@ -54,7 +54,7 @@ module CouchRest # XXX: HACK seems hacky to do this on every validation, probably should # do this elsewhere? - field = CouchRest.humanize(field_name) + field = field_name.to_s.humanize min = @range ? @range.min : @min max = @range ? @range.max : @max equal = @equal diff --git a/spec/couchrest/extended_doc_attachment_spec.rb b/spec/couchrest/extended_doc_attachment_spec.rb index 98dc86b..30f94dd 100644 --- a/spec/couchrest/extended_doc_attachment_spec.rb +++ b/spec/couchrest/extended_doc_attachment_spec.rb @@ -68,6 +68,19 @@ describe "ExtendedDocument attachments" do @obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type) @obj['_attachments'][@attachment_name]['content_type'].should == @content_type end + + it "should detect the content-type automatically" do + @obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png") + @obj['_attachments']['couchdb.png']['content_type'].should == "image/png" + end + + it "should use name to detect the content-type automatically if no file" do + file = File.open(FIXTURE_PATH + '/attachments/couchdb.png') + file.stub!(:path).and_return("badfilname") + @obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png") + @obj['_attachments']['couchdb.png']['content_type'].should == "image/png" + end + end describe 'reading, updating, and deleting an attachment' do @@ -96,7 +109,7 @@ describe "ExtendedDocument attachments" do reloaded_obj.read_attachment(@attachment_name).should == file.read end - it 'should se the content-type if passed' do + it 'should set the content-type if passed' do file = File.open(FIXTURE_PATH + '/attachments/README') @file.should_not == file @obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type) diff --git a/spec/couchrest/extended_doc_spec.rb b/spec/couchrest/extended_doc_spec.rb index f138d4b..e03f1a1 100644 --- a/spec/couchrest/extended_doc_spec.rb +++ b/spec/couchrest/extended_doc_spec.rb @@ -164,6 +164,25 @@ describe "ExtendedDocument" do doc.run_after_save.should be_true end end + + describe "creating a new document from database" do + + it "should instantialize" do + doc = Article.create_from_database({'_id' => 'testitem1', '_rev' => 123, 'couchrest-type' => 'Article', 'name' => 'my test'}) + doc.class.should eql(Article) + end + + it "should instantialize of same class if no couchrest-type included from DB" do + doc = Article.create_from_database({'_id' => 'testitem1', '_rev' => 123, 'name' => 'my test'}) + doc.class.should eql(Article) + end + + it "should instantialize document of different type" do + doc = Article.create_from_database({'_id' => 'testitem2', '_rev' => 123, 'couchrest-type' => 'WithCallBacks', 'name' => 'my test'}) + doc.class.should eql(WithCallBacks) + end + + end describe "update attributes without saving" do before(:each) do