davber_couch_potato 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGES.md CHANGED
@@ -1,7 +1,24 @@
1
1
  ## Changes
2
2
 
3
- ### 0.3.0 - cloned gem
3
+ ### 0.4.0
4
+ * ruby 1.9.2 compatibility (langalex)
5
+ * couch potato objects now behave correctly when used as keys in Hashes (langalex)
6
+ * use as\_json instead of to\_s(:json), which is the rails way
7
+ * use ActiveModel dirty tracking (langalex) - this means no more "deep tracking", e.g. `user.tags << 'new_tag'; user.dirty? # false`
8
+
9
+ ### 0.3.2
10
+ * support yielding to blocks on #initialize (martinrehfeld)
11
+ * support for negative numbers in Fixnum/Float properties (langalex)
12
+
13
+ ### 0.3.1
14
+ * ActiveModel callbacks (kazjote)
15
+ * do not use Rails.env in initializer as it will free Rails.env for all times and in Rails 2.3.x apps it will be called too early thus always beeing development (jweiss)
16
+ * ruby 1.9.2 compatibility (langalex)
17
+ * can configure validation framework in couchdb.yml, process couchdb.yml with erb (langalex)
18
+
19
+ ### 0.3.0 - cloned gem!
4
20
  * type field of documents now configurable, by using CouchPotato.type_field (davber)
21
+ * the mapping from class to design document name is now configurable, by using CouchPotato.design_name_fun, which is supposed to be a Proc (davber)
5
22
 
6
23
  ### 0.3.0
7
24
  * support for lists (langalex)
data/README.md CHANGED
@@ -22,6 +22,13 @@ Lastly Couch Potato aims to provide a seamless integration with Ruby on Rails, e
22
22
  * declarative views with either custom or generated map/reduce functions
23
23
  * extensive spec suite
24
24
 
25
+ ### Supported Environments
26
+
27
+ * Ruby 1.8.7, 1.9.1, 1.9.2
28
+ * CouchDB 1.0.1
29
+
30
+ (Supported means I run the specs against those before releasing a new gem.)
31
+
25
32
  ### Installation
26
33
 
27
34
  Couch Potato is hosted as a gem which you can install like this:
@@ -51,18 +58,40 @@ Optionally you can configure which framework you want to use for validations (ei
51
58
 
52
59
  #### Using with Rails
53
60
 
54
- Add to your config/environment.rb:
61
+ Create a config/couchdb.yml:
62
+
63
+ default: &default
64
+ validation_framework: :active_model #optional
65
+
66
+ development:
67
+ <<: *default
68
+ database: development_db_name
69
+ test:
70
+ <<: *default
71
+ database: test_db_name
72
+ production:
73
+ <<: *default
74
+ database: <%= ENV['DB_NAME'] %>
75
+
76
+ #### Rails 2.x
77
+
78
+ Add to your _config/environment.rb_:
55
79
 
56
80
  config.gem 'davber_couch_potato', :source => 'http://gemcutter.org'
57
81
  config.frameworks -= [:active_record] # if you switch completely
82
+
83
+ #### Rails 3.x
58
84
 
59
- Then create a config/couchdb.yml:
85
+ Add to your _Gemfile_:
60
86
 
61
- development: development_db_name
62
- test: test_db_name
63
- production: http://db.server/production_db_name
87
+ # gem 'rails' # we don't want to load activerecord so we can't require rails
88
+ gem 'railties'
89
+ gem 'actionpack'
90
+ gem 'actionmailer'
91
+ gem 'activemodel'
92
+ gem "couch_potato"
64
93
 
65
- Alternatively you can also install Couch Potato directly as a plugin.
94
+ Note: please make sure that when you run `Date.today.as_json` in the Rails console it returns something like `2010/12/10` and not `2010-12-10` - if it does another gem has overwritten Couch Potato's Date patches - in this case move Couch Potato further down in your Gemfile or whereever you load it.
66
95
 
67
96
  ### Introduction
68
97
 
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 3
3
+ :minor: 4
4
4
  :patch: 0
5
5
  :build:
data/lib/core_ext/date.rb CHANGED
@@ -1,18 +1,11 @@
1
1
  class Date
2
2
  def to_json(*a)
3
- %("#{to_s(:json)}")
3
+ %("#{as_json}")
4
4
  end
5
5
 
6
- def to_s_with_json(*args)
7
- if args[0] == :json
8
- strftime("%Y/%m/%d")
9
- else
10
- to_s_without_json *args
11
- end
6
+ def as_json(*args)
7
+ strftime("%Y/%m/%d")
12
8
  end
13
- alias_method :to_s_without_json, :to_s
14
- alias_method :to_s, :to_s_with_json
15
-
16
9
 
17
10
  def self.json_create string
18
11
  return nil if string.nil?
@@ -4,5 +4,9 @@ module ActiveSupportMethods
4
4
  $1.upcase
5
5
  end
6
6
  end
7
+
8
+ def blank?
9
+ empty?
10
+ end
7
11
  end
8
- String.send :include, ActiveSupportMethods unless String.new.respond_to?(:underscore)
12
+ String.send :include, ActiveSupportMethods unless String.new.respond_to?(:underscore)
data/lib/core_ext/time.rb CHANGED
@@ -1,17 +1,11 @@
1
1
  class Time
2
2
  def to_json(*a)
3
- %("#{to_s(:json)}")
3
+ %("#{as_json}")
4
4
  end
5
5
 
6
- def to_s_with_json(*args)
7
- if args[0] == :json
8
- getutc.strftime("%Y/%m/%d %H:%M:%S +0000")
9
- else
10
- to_s_without_json *args
11
- end
6
+ def as_json(*args)
7
+ getutc.strftime("%Y/%m/%d %H:%M:%S +0000")
12
8
  end
13
- alias_method :to_s_without_json, :to_s
14
- alias_method :to_s, :to_s_with_json
15
9
 
16
10
  def self.json_create string
17
11
  return nil if string.nil?
@@ -63,7 +63,7 @@ module CouchPotato
63
63
 
64
64
  # saves a document. returns true on success, false on failure
65
65
  def save_document(document, validate = true)
66
- return true unless document.dirty?
66
+ return true unless document.dirty? || document.new?
67
67
  if document.new?
68
68
  create_document(document, validate)
69
69
  else
@@ -79,10 +79,10 @@ module CouchPotato
79
79
  alias_method :save!, :save_document!
80
80
 
81
81
  def destroy_document(document)
82
- document.run_callbacks :before_destroy
83
- document._deleted = true
84
- database.delete_doc document.to_hash
85
- document.run_callbacks :after_destroy
82
+ document.run_callbacks :destroy do
83
+ document._deleted = true
84
+ database.delete_doc document.to_hash
85
+ end
86
86
  document._id = nil
87
87
  document._rev = nil
88
88
  end
@@ -102,7 +102,7 @@ module CouchPotato
102
102
  alias_method :load, :load_document
103
103
 
104
104
  def inspect #:nodoc:
105
- "#<CouchPotato::Database>"
105
+ "#<CouchPotato::Database @root=\"#{database.root}\">"
106
106
  end
107
107
 
108
108
  private
@@ -112,35 +112,39 @@ module CouchPotato
112
112
 
113
113
  if validate
114
114
  document.errors.clear
115
- document.run_callbacks :before_validation_on_save
116
- document.run_callbacks :before_validation_on_create
117
- return false unless valid_document?(document)
115
+ document.run_callbacks :validation_on_save do
116
+ document.run_callbacks :validation_on_create do
117
+ return false unless valid_document?(document)
118
+ end
119
+ end
118
120
  end
119
121
 
120
- document.run_callbacks :before_save
121
- document.run_callbacks :before_create
122
- res = database.save_doc document.to_hash
123
- document._rev = res['rev']
124
- document._id = res['id']
125
- document.run_callbacks :after_save
126
- document.run_callbacks :after_create
122
+ document.run_callbacks :save do
123
+ document.run_callbacks :create do
124
+ res = database.save_doc document.to_hash
125
+ document._rev = res['rev']
126
+ document._id = res['id']
127
+ end
128
+ end
127
129
  true
128
130
  end
129
131
 
130
132
  def update_document(document, validate)
131
133
  if validate
132
134
  document.errors.clear
133
- document.run_callbacks :before_validation_on_save
134
- document.run_callbacks :before_validation_on_update
135
- return false unless valid_document?(document)
135
+ document.run_callbacks :validation_on_save do
136
+ document.run_callbacks :validation_on_update do
137
+ return false unless valid_document?(document)
138
+ end
139
+ end
140
+ end
141
+
142
+ document.run_callbacks :save do
143
+ document.run_callbacks :update do
144
+ res = database.save_doc document.to_hash
145
+ document._rev = res['rev']
146
+ end
136
147
  end
137
-
138
- document.run_callbacks :before_save
139
- document.run_callbacks :before_update
140
- res = database.save_doc document.to_hash
141
- document._rev = res['rev']
142
- document.run_callbacks :after_save
143
- document.run_callbacks :after_update
144
148
  true
145
149
  end
146
150
 
@@ -158,4 +162,4 @@ module CouchPotato
158
162
  end
159
163
 
160
164
  end
161
- end
165
+ end
@@ -18,9 +18,10 @@ module CouchPotato
18
18
  module Persistence
19
19
 
20
20
  def self.included(base) #:nodoc:
21
- base.send :include, Properties, Callbacks, Validation, Json, CouchPotato::View::CustomViews, CouchPotato::View::Lists
21
+ base.send :include, Properties, Callbacks, Json, CouchPotato::View::CustomViews, CouchPotato::View::Lists
22
22
  base.send :include, DirtyAttributes, GhostAttributes, Attachments
23
23
  base.send :include, MagicTimestamps, ActiveModelCompliance
24
+ base.send :include, Validation
24
25
  base.class_eval do
25
26
  attr_accessor :_id, :_rev, :_deleted, :database
26
27
  alias_method :id, :_id
@@ -29,7 +30,8 @@ module CouchPotato
29
30
  end
30
31
 
31
32
  # initialize a new instance of the model optionally passing it a hash of attributes.
32
- # the attributes have to be declared using the #property method
33
+ # the attributes have to be declared using the #property method.
34
+ # the new model will be yielded to an optionally given block.
33
35
  #
34
36
  # example:
35
37
  # class Book
@@ -37,11 +39,20 @@ module CouchPotato
37
39
  # property :title
38
40
  # end
39
41
  # book = Book.new :title => 'Time to Relax'
42
+ #
43
+ # OR
44
+ #
45
+ # book = Book.new do |b|
46
+ # b.title = 'Time to Relax'
47
+ # end
40
48
  # book.title # => 'Time to Relax'
41
49
  def initialize(attributes = {})
42
- attributes.each do |name, value|
43
- self.send("#{name}=", value)
44
- end if attributes
50
+ if attributes
51
+ attributes.each do |name, value|
52
+ self.send("#{name}=", value)
53
+ end
54
+ end
55
+ yield self if block_given?
45
56
  end
46
57
 
47
58
  # assign multiple attributes at once.
@@ -96,6 +107,14 @@ module CouchPotato
96
107
  def ==(other) #:nodoc:
97
108
  other.class == self.class && self.to_json == other.to_json
98
109
  end
110
+
111
+ def eql?(other)
112
+ self == other
113
+ end
114
+
115
+ def hash
116
+ _id.hash * (_id.hash.to_s.size ** 10) + _rev.hash
117
+ end
99
118
 
100
119
  def inspect
101
120
  attributes_as_string = attributes.map {|attribute, value| "#{attribute}: #{value.inspect}"}.join(", ")
@@ -1,62 +1,29 @@
1
+ require 'active_support/concern'
2
+ require 'active_model/callbacks'
3
+
1
4
  module CouchPotato
2
5
  module Persistence
3
6
  module Callbacks
4
7
  def self.included(base) #:nodoc:
5
- base.extend ClassMethods
8
+ base.extend ActiveModel::Callbacks
6
9
 
7
10
  base.class_eval do
8
11
  attr_accessor :skip_callbacks
9
- def self.callbacks #:nodoc:
10
- @callbacks ||= {:before_validation => [], :before_validation_on_create => [],
11
- :before_validation_on_update => [], :before_validation_on_save => [], :before_create => [],
12
- :after_create => [], :before_update => [], :after_update => [],
13
- :before_save => [], :after_save => [],
14
- :before_destroy => [], :after_destroy => []}
15
- end
12
+
13
+ define_model_callbacks :create, :save, :update, :destroy
14
+ define_model_callbacks *[:save, :create, :update].map {|c| :"validation_on_#{c}"}
15
+ define_model_callbacks :validation unless Config.validation_framework == :active_model
16
16
  end
17
17
  end
18
18
 
19
19
  # Runs all callbacks on a model with the given name, e.g. :after_create.
20
20
  #
21
21
  # This method is called by the CouchPotato::Database object when saving/destroying an object
22
- def run_callbacks(name)
22
+ def run_callbacks(name, &block)
23
23
  return if skip_callbacks
24
-
25
- callbacks = self.class.ancestors.map do |clazz|
26
- clazz.callbacks[name] if clazz.respond_to?(:callbacks)
27
- end.flatten.compact.uniq
28
-
29
- callbacks.each do |callback|
30
- if [Symbol, String].include?(callback.class)
31
- send callback
32
- elsif callback.is_a?(Proc)
33
- callback.call self
34
- else
35
- raise "Don't know how to handle callback of type #{callback.class.name}"
36
- end
37
- end
38
- end
39
24
 
40
- module ClassMethods
41
- [
42
- :before_validation,
43
- :before_validation_on_create,
44
- :before_validation_on_update,
45
- :before_validation_on_save,
46
- :before_create,
47
- :before_save,
48
- :before_update,
49
- :before_destroy,
50
- :after_update,
51
- :after_save,
52
- :after_create,
53
- :after_destroy
54
- ].each do |callback|
55
- define_method callback do |*names|
56
- callbacks[callback].push *names
57
- end
58
- end
25
+ send(:"_run_#{name}_callbacks", &block)
59
26
  end
60
27
  end
61
28
  end
62
- end
29
+ end
@@ -4,21 +4,20 @@ module CouchPotato
4
4
  module DirtyAttributes
5
5
 
6
6
  def self.included(base) #:nodoc:
7
+ base.send :include, ActiveModel::Dirty
7
8
  base.class_eval do
8
9
  after_save :reset_dirty_attributes
9
-
10
- def initialize(attributes = {})
11
- super
12
- assign_attribute_copies_for_dirty_tracking
13
- end
14
10
  end
15
11
  end
16
12
 
13
+ def initialize(attributes = {})
14
+ super
15
+ # assign_attribute_copies_for_dirty_tracking
16
+ end
17
+
17
18
  # returns true if a model has dirty attributes, i.e. their value has changed since the last save
18
19
  def dirty?
19
- new? || @forced_dirty || self.class.properties.inject(false) do |res, property|
20
- res || property.dirty?(self)
21
- end
20
+ changed? || @forced_dirty
22
21
  end
23
22
 
24
23
  # marks a model as dirty
@@ -26,19 +25,21 @@ module CouchPotato
26
25
  @forced_dirty = true
27
26
  end
28
27
 
29
- private
30
-
31
- def assign_attribute_copies_for_dirty_tracking
32
- attributes.each do |name, value|
33
- self.instance_variable_set("@#{name}_was", clone_attribute(value))
34
- end if attributes
28
+ def method_missing(name, *args)
29
+ if(name.to_s.include?('_will_change!'))
30
+ self.class.define_attribute_methods self.class.property_names
31
+ send(name, *args)
32
+ else
33
+ super
34
+ end
35
35
  end
36
36
 
37
+ private
38
+
37
39
  def reset_dirty_attributes
40
+ @previously_changed = changes
41
+ @changed_attributes.clear
38
42
  @forced_dirty = nil
39
- self.class.properties.each do |property|
40
- instance_variable_set("@#{property.name}_was", clone_attribute(send(property.name)))
41
- end
42
43
  end
43
44
 
44
45
  def clone_attribute(value)
@@ -3,20 +3,25 @@ module CouchPotato
3
3
  def self.included(base)
4
4
  base.class_eval do
5
5
  attr_accessor :_document
6
- def self.json_create(json)
7
- instance = super
6
+ def self.json_create_with_ghost(json)
7
+ instance = json_create_without_ghost(json)
8
8
  instance._document = json if json
9
9
  instance
10
10
  end
11
11
 
12
- def method_missing(name, *args)
13
- if(value = _document && _document[name.to_s])
14
- value
15
- else
16
- super
17
- end
12
+ class << self
13
+ alias_method_chain :json_create, :ghost
18
14
  end
19
15
  end
20
16
  end
17
+
18
+ def method_missing(name, *args)
19
+ if(value = _document && _document[name.to_s])
20
+ value
21
+ else
22
+ super
23
+ end
24
+ end
21
25
  end
22
- end
26
+ end
27
+
@@ -35,9 +35,12 @@ module CouchPotato
35
35
  instance = self.new
36
36
  instance._id = json[:_id] || json['_id']
37
37
  instance._rev = json[:_rev] || json['_rev']
38
+ instance.instance_variable_set('@skip_dirty_tracking', true)
38
39
  properties.each do |property|
39
40
  property.build(instance, json)
40
41
  end
42
+ instance.instance_variable_set('@skip_dirty_tracking', false)
43
+ # instance.instance_variable_get("@changed_attributes").clear if instance.instance_variable_get("@changed_attributes")
41
44
  instance
42
45
  end
43
46
  end
@@ -7,13 +7,14 @@ module CouchPotato
7
7
 
8
8
  before_create lambda {|model|
9
9
  model.created_at ||= Time.now
10
- model.created_at_not_changed
10
+ @changed_attributes.delete 'created_at'
11
11
  model.updated_at ||= Time.now
12
- model.updated_at_not_changed
12
+ @changed_attributes.delete 'updated_at'
13
13
  }
14
14
  before_update lambda {|model|
15
15
  model.updated_at = Time.now
16
- model.updated_at_not_changed}
16
+ @changed_attributes.delete 'updated_at'
17
+ }
17
18
  end
18
19
  end
19
20
  end
@@ -59,13 +59,6 @@ module CouchPotato
59
59
  properties.map(&:name)
60
60
  end
61
61
 
62
- def json_create(json) #:nodoc:
63
- return if json.nil?
64
- instance = super
65
- instance.send(:assign_attribute_copies_for_dirty_tracking)
66
- instance
67
- end
68
-
69
62
  # Declare a property on a model class. Properties are not typed by default.
70
63
  # You can store anything in a property that can be serialized into JSON.
71
64
  # If you want a property to be of a custom class you have to define it using the :type option.
@@ -37,8 +37,6 @@ module CouchPotato
37
37
 
38
38
  def define_accessors(base, name, options)
39
39
  base.class_eval do
40
- attr_reader "#{name}_was"
41
-
42
40
  define_method "#{name}" do
43
41
  value = self.instance_variable_get("@#{name}")
44
42
  if value.nil? && options[:default]
@@ -51,20 +49,14 @@ module CouchPotato
51
49
  end
52
50
 
53
51
  define_method "#{name}=" do |value|
54
- self.instance_variable_set("@#{name}", type_caster.cast(value, options[:type]))
52
+ typecasted_value = type_caster.cast(value, options[:type])
53
+ send("#{name}_will_change!") unless @skip_dirty_tracking || typecasted_value == send(name)
54
+ self.instance_variable_set("@#{name}", typecasted_value)
55
55
  end
56
56
 
57
57
  define_method "#{name}?" do
58
58
  !self.send(name).nil? && !self.send(name).try(:blank?)
59
59
  end
60
-
61
- define_method "#{name}_changed?" do
62
- !self.instance_variable_get("@#{name}_not_changed") && self.send(name) != self.send("#{name}_was")
63
- end
64
-
65
- define_method "#{name}_not_changed" do
66
- self.instance_variable_set("@#{name}_not_changed", true)
67
- end
68
60
  end
69
61
  end
70
62
  end
@@ -3,15 +3,15 @@ module CouchPotato
3
3
  class TypeCaster #:nodoc:
4
4
  def cast(value, type)
5
5
  if type == :boolean
6
- cast_boolen(value)
6
+ cast_boolean(value)
7
7
  else
8
8
  cast_native(value, type)
9
9
  end
10
10
  end
11
-
11
+
12
12
  private
13
-
14
- def cast_boolen(value)
13
+
14
+ def cast_boolean(value)
15
15
  if [FalseClass, TrueClass].include?(value.class) || value.nil?
16
16
  value
17
17
  elsif [0, '0'].include?(value)
@@ -20,13 +20,13 @@ module CouchPotato
20
20
  true
21
21
  end
22
22
  end
23
-
23
+
24
24
  def cast_native(value, type)
25
25
  if type && !value.instance_of?(type)
26
26
  if type == Fixnum
27
- value.to_s.scan(/\d/).join.to_i unless value.blank?
27
+ value.to_s.scan(/-?\d+/).join.to_i unless value.blank?
28
28
  elsif type == Float
29
- value.to_s.scan(/\d+\.?\d*/).join.to_f unless value.blank?
29
+ value.to_s.scan(/-?\d+\.?\d*/).join.to_f unless value.blank?
30
30
  else
31
31
  type.json_create value unless value.blank?
32
32
  end
@@ -34,7 +34,7 @@ module CouchPotato
34
34
  value
35
35
  end
36
36
  end
37
-
37
+
38
38
  end
39
39
  end
40
- end
40
+ end
@@ -1,18 +1,24 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../rails/reload_classes')
2
+ require 'erb'
2
3
 
3
4
  module CouchPotato
4
5
  def self.rails_init
5
- CouchPotato::Config.database_name = YAML::load(File.read(Rails.root.join('config/couchdb.yml')))[Rails.env]
6
+ config = YAML::load(ERB.new(File.read(Rails.root.join('config/couchdb.yml'))).result)[RAILS_ENV]
7
+ if config.is_a?(String)
8
+ CouchPotato::Config.database_name = config
9
+ else
10
+ CouchPotato::Config.database_name = config['database']
11
+ CouchPotato::Config.validation_framework = config['validation_framework']
12
+ end
6
13
  end
7
14
 
8
15
  if defined?(::Rails::Railtie)
9
16
  class Railtie < ::Rails::Railtie
10
- config.after_initialize do |app|
17
+ initializer 'couch_potato.load_config' do |app|
11
18
  CouchPotato.rails_init
12
19
  end
13
20
  end
14
21
  else
15
22
  rails_init
16
23
  end
17
-
18
24
  end
@@ -11,16 +11,20 @@ module CouchPotato
11
11
  base.send :include, ::Validatable
12
12
  base.class_eval do
13
13
  # Override the validate method to first run before_validation callback
14
- def valid?
14
+ def valid_with_before_validation_callback?
15
15
  errors.clear
16
- run_callbacks :before_validation
17
- before_validation_errors = errors.errors.dup
18
- super
19
- before_validation_errors.each do |k, v|
20
- v.each {|message| errors.add(k, message)}
16
+ run_callbacks :validation do
17
+ before_validation_errors = errors.errors.dup
18
+ valid_without_before_validation_callback?
19
+ before_validation_errors.each do |k, v|
20
+ v.each {|message| errors.add(k, message)}
21
+ end
21
22
  end
22
23
  errors.empty?
23
24
  end
25
+
26
+ alias_method :valid_without_before_validation_callback?, :valid?
27
+ alias_method :valid?, :valid_with_before_validation_callback?
24
28
  end
25
29
  end
26
30
  end
@@ -1,4 +1,16 @@
1
1
  module CouchPotato
2
+ # We can bypass creation of views completely, so we can use views
3
+ # that are created via other means, such as CouchApp.
4
+ # The default is not to bypass creation.
5
+ # TODO: enable this bypass for specific view in the view specification, instead of globally
6
+ def self.bypass_view_creation
7
+ @bypass_view_creation
8
+ end
9
+
10
+ def self.bypass_view_creation= flag
11
+ @bypass_view_creation = flag
12
+ end
13
+
2
14
  module View
3
15
  # Used to query views (and create them if they don't exist). Usually you won't have to use this class directly. Instead it is used internally by the CouchPotato::Database.view method.
4
16
  class ViewQuery
@@ -15,7 +27,7 @@ module CouchPotato
15
27
  end
16
28
 
17
29
  def query_view!(parameters = {})
18
- update_view unless view_has_been_updated?
30
+ update_view unless CouchPotato::bypass_view_creation || view_has_been_updated?
19
31
  begin
20
32
  query_view parameters
21
33
  rescue RestClient::ResourceNotFound
@@ -37,7 +49,9 @@ module CouchPotato
37
49
  design_doc['lists'] ||= {}
38
50
  design_doc['lists'][@list_name.to_s] = @list_function
39
51
  end
40
- @database.save_doc(design_doc) if original_views != design_doc['views'] || original_lists != design_doc['lists']
52
+ # We do not save the design document if view creation is bypassed
53
+ @database.save_doc(design_doc) unless CouchPotato::bypass_view_creation ||
54
+ original_views == design_doc['views'] && original_lists == design_doc['lists']
41
55
  end
42
56
 
43
57
  def view_functions
@@ -49,14 +63,17 @@ module CouchPotato
49
63
  end
50
64
 
51
65
  def view_has_been_updated?
66
+ # TODO: decide what to do here in case CouchPotato::bypass_view_creation is true
52
67
  updated_views[[@design_document_name, @view_name]]
53
68
  end
54
69
 
55
70
  def view_updated
71
+ # TODO: decide what to do here in case CouchPotato::bypass_view_creation is true
56
72
  updated_views[[@design_document_name, @view_name]] = true
57
73
  end
58
74
 
59
75
  def updated_views
76
+ # TODO: decide what to do here in case CouchPotato::bypass_view_creation is true
60
77
  @@updated_views ||= {}
61
78
  @@updated_views
62
79
  end
@@ -7,7 +7,7 @@ module CouchPotato
7
7
  begin
8
8
  yield
9
9
  rescue ArgumentError => e
10
- if(name = e.message.scan(/(can't find const|undefined class\/module) ([\w\:]+)/).first[1])
10
+ if(name = e.message.scan(/(can't find const|undefined class\/module) ([\w\:]+)/).try(:first).try(:[], 1))
11
11
  eval name.gsub(/\:+$/, '')
12
12
  retry
13
13
  else
@@ -22,9 +22,6 @@ class CallbackRecorder
22
22
 
23
23
  view :all, :key => :required_property
24
24
 
25
- attr_accessor :lambda_works
26
- before_create lambda {|model| model.lambda_works = true }
27
-
28
25
  def callbacks
29
26
  @callbacks ||= []
30
27
  end
@@ -58,7 +55,7 @@ describe "multiple callbacks at once" do
58
55
 
59
56
  it "should run all callback methods given to the callback method call" do
60
57
  monkey = Monkey.new
61
- monkey.run_callbacks :before_create
58
+ monkey.run_callbacks :create
62
59
  monkey.eaten_banana.should be_true
63
60
  monkey.eaten_apple.should be_true
64
61
  end
@@ -263,14 +260,6 @@ describe "destroy callbacks" do
263
260
  end
264
261
  end
265
262
 
266
- describe "lambda callbacks" do
267
- it "should run the lambda" do
268
- recorder = CallbackRecorder.new
269
- recorder.run_callbacks :before_create
270
- recorder.lambda_works.should be_true
271
- end
272
- end
273
-
274
263
  describe "validation callbacks" do
275
264
  class ValidatedUser
276
265
  include CouchPotato::Persistence
@@ -305,4 +294,4 @@ describe "validation callbacks" do
305
294
  user.valid?.should == true
306
295
  user.errors.on(:name).should == nil
307
296
  end
308
- end
297
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
- require File.join(File.dirname(__FILE__), 'fixtures', 'address')
3
- require File.join(File.dirname(__FILE__), 'fixtures', 'person')
2
+ require 'fixtures/address'
3
+ require 'fixtures/person'
4
4
 
5
5
  class Watch
6
6
  include CouchPotato::Persistence
data/spec/rails_spec.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'spec_helper'
2
- require File.dirname(__FILE__) + '/../rails/reload_classes'
3
2
 
4
3
  module LoadConst
5
4
  def const_missing(name)
@@ -33,7 +32,7 @@ describe CouchPotato::Database, 'rails specific behavior' do
33
32
  CouchPotato.couchrest_database.save_doc(JSON.create_id => 'Autoloader::Uninitialized', '_id' => '1')
34
33
  CouchPotato.database.load('1').class.name.should == 'Autoloader::Uninitialized'
35
34
  end
36
-
35
+
37
36
  it "should load nested models" do
38
37
  CouchPotato.couchrest_database.save_doc(JSON.create_id => 'Autoloader::Nested::Nested2', '_id' => '1')
39
38
  CouchPotato.database.load('1').class.name.should == 'Autoloader::Nested::Nested2'
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+ require 'yaml'
3
+ require 'spec/mocks'
4
+
5
+ RAILS_ENV = 'test'
6
+ module Rails
7
+ class Railtie
8
+ def self.initializer(*args)
9
+ end
10
+ end
11
+
12
+ def self.root
13
+ Spec::Mocks::Mock.new :join => ''
14
+ end
15
+ end
16
+
17
+ require 'couch_potato/railtie'
18
+
19
+ describe "railtie" do
20
+ before(:all) do
21
+ @validation_framework = CouchPotato::Config.validation_framework
22
+ end
23
+
24
+ after(:all) do
25
+ CouchPotato::Config.validation_framework = @validation_framework
26
+ end
27
+
28
+ context 'yaml file contains only database names' do
29
+ it "should set the database name from the yaml file" do
30
+ File.stub(:read => "test: test_db")
31
+
32
+ CouchPotato::Config.should_receive(:database_name=).with('test_db')
33
+
34
+ CouchPotato.rails_init
35
+ end
36
+ end
37
+
38
+ context 'yaml file contains more configuration' do
39
+ before(:each) do
40
+ File.stub(:read => "test: \n database: test_db\n validation_framework: :active_model")
41
+ end
42
+
43
+ it "should set the database name from the yaml file" do
44
+ CouchPotato::Config.should_receive(:database_name=).with('test_db')
45
+
46
+ CouchPotato.rails_init
47
+ end
48
+
49
+ it "should set the validation framework from the yaml file" do
50
+ CouchPotato::Config.should_receive(:validation_framework=).with(:active_model)
51
+
52
+ CouchPotato.rails_init
53
+ end
54
+ end
55
+
56
+ it "should process the yml file with erb" do
57
+ File.stub(:read => "test: \n database: <%= 'db' %>")
58
+
59
+ CouchPotato::Config.should_receive(:database_name=).with('db')
60
+
61
+ CouchPotato.rails_init
62
+ end
63
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'spec'
3
+ require 'time'
3
4
 
4
5
  $:.unshift(File.dirname(__FILE__) + '/../lib')
5
6
 
@@ -58,6 +58,11 @@ describe "attributes" do
58
58
  @plant.typed_leaf_count.should == 4
59
59
  end
60
60
 
61
+ it "should convert a string into a negative fixnum" do
62
+ @plant.typed_leaf_count = '-4'
63
+ @plant.typed_leaf_count.should == -4
64
+ end
65
+
61
66
  it "should leave a fixnum as is" do
62
67
  @plant.typed_leaf_count = 4
63
68
  @plant.typed_leaf_count.should == 4
@@ -94,6 +99,11 @@ describe "attributes" do
94
99
  @plant.typed_leaf_size = '5'
95
100
  @plant.typed_leaf_size.should == 5.0
96
101
  end
102
+
103
+ it "should convert a negative number in a string" do
104
+ @plant.typed_leaf_size = '-5.0'
105
+ @plant.typed_leaf_size.should == -5.0
106
+ end
97
107
 
98
108
  it "should leave a float as it is" do
99
109
  @plant.typed_leaf_size = 0.5
@@ -4,23 +4,15 @@ describe 'callbacks' do
4
4
  class Tree
5
5
  include CouchPotato::Persistence
6
6
 
7
- before_validation :grow_leaf, 'grow_branch', lambda {|tree| tree.root_count ||= 0; tree.root_count += 1 }
7
+ before_validation :grow_leaf
8
8
 
9
9
  property :leaf_count
10
- property :root_count
11
- property :branch_count
12
10
  property :watered
13
-
14
11
 
15
12
  def grow_leaf
16
13
  self.leaf_count ||= 0
17
14
  self.leaf_count += 1
18
15
  end
19
-
20
- def grow_branch
21
- self.branch_count ||= 0
22
- self.branch_count += 1
23
- end
24
16
  end
25
17
 
26
18
  class AppleTree < Tree
@@ -37,31 +29,13 @@ describe 'callbacks' do
37
29
  end
38
30
  end
39
31
 
40
- it "should call a method from a symbol when validated" do
41
- tree = Tree.new(:leaf_count => 1, :root_count => 1)
42
- tree.valid?
43
- tree.leaf_count.should == 2
44
- end
45
-
46
- it "should call a method from a string when validated" do
47
- tree = Tree.new(:branch_count => 0)
48
- tree.valid?
49
- tree.branch_count.should == 1
50
- end
51
-
52
- it "should call a lambda when validated" do
53
- tree = Tree.new(:leaf_count => 1, :root_count => 1)
54
- tree.valid?
55
- tree.root_count.should == 2
56
- end
57
-
58
32
  context 'inheritance' do
59
33
  it "should call the callbacks of the super class" do
60
34
  tree = AppleTree.new :leaf_count => 1
61
35
  tree.valid?
62
36
  tree.leaf_count.should == 2
63
37
  end
64
-
38
+
65
39
  it "should call the callbacks of the child class" do
66
40
  tree = AppleTree.new :leaf_count => 1
67
41
  tree.valid?
@@ -69,4 +43,4 @@ describe 'callbacks' do
69
43
  end
70
44
  end
71
45
 
72
- end
46
+ end
@@ -119,10 +119,10 @@ describe CouchPotato::Database, 'save_document' do
119
119
  it "should not run the validations when saved with false" do
120
120
  category = Category.new(:name => 'food')
121
121
  @db.save_document(category)
122
- category.new?.should == false
122
+ category.new?.should be_false
123
123
  category.name = nil
124
124
  @db.save_document(category, false)
125
- category.dirty?.should == false
125
+ category.dirty?.should be_false
126
126
  end
127
127
 
128
128
  it "should run the validations when saved with true" do
@@ -7,10 +7,10 @@ describe Date, 'to_json' do
7
7
  end
8
8
  end
9
9
 
10
- describe Date, 'to_s(:json)' do
10
+ describe Date, 'as_json' do
11
11
  it "should format it in the same way as to_json does so i can use this to do queries over date attributes" do
12
12
  date = Date.parse('2009-01-01')
13
- date.to_s(:json).should == "2009/01/01"
13
+ date.as_json.should == "2009/01/01"
14
14
  end
15
15
  end
16
16
 
@@ -33,24 +33,6 @@ describe 'dirty attribute tracking' do
33
33
  @couchrest_db.should_receive(:save_doc)
34
34
  @db.save_document(plate)
35
35
  end
36
-
37
- it "should correctly track dirty hashes (deep clone)" do
38
- plate = Plate.new :food => {:veggies => ['carrots', 'peas']}
39
- @db.save_document(plate)
40
- plate.food[:veggies] << 'beans'
41
- @couchrest_db.should_receive(:save_doc)
42
- @db.save_document(plate)
43
- end
44
-
45
- it "should correctly track dirty hashes (deep clone) after a save" do
46
- plate = Plate.new :food => {:veggies => ['carrots', 'peas']}
47
- @db.save_document(plate)
48
- plate.food[:veggies] << 'beans'
49
- @db.save_document(plate)
50
- plate.food[:veggies] << 'cauliflower'
51
- @couchrest_db.should_receive(:save_doc)
52
- @db.save_document(plate)
53
- end
54
36
  end
55
37
 
56
38
  describe "newly created object" do
@@ -67,21 +49,22 @@ describe 'dirty attribute tracking' do
67
49
 
68
50
  describe "with type BigDecimal" do
69
51
  before(:each) do
70
- class ::Plate
52
+ class Bowl
53
+ include CouchPotato::Persistence
71
54
  property :price
72
55
  end
73
56
  end
74
57
  it "should not dup BigDecimal" do
75
58
 
76
59
  lambda {
77
- Plate.new :price => BigDecimal.new("5.23")
60
+ Bowl.new :price => BigDecimal.new("5.23")
78
61
  }.should_not raise_error(TypeError)
79
62
  end
80
63
 
81
64
  it "should return the old value" do
82
- plate = Plate.new :price => BigDecimal.new("5.23")
83
- plate.price = BigDecimal.new("2.23")
84
- plate.price_was.should == 5.23
65
+ bowl = Bowl.new :price => BigDecimal.new("5.23")
66
+ bowl.price = BigDecimal.new("2.23")
67
+ bowl.price_was.should == 5.23
85
68
  end
86
69
 
87
70
  end
@@ -94,13 +77,7 @@ describe 'dirty attribute tracking' do
94
77
  end
95
78
 
96
79
  it "should return false if attribute not changed" do
97
- @plate.should_not be_food_changed
98
- end
99
-
100
- it "should return false if attribute forced not changed" do
101
- @plate.food = 'burger'
102
- @plate.food_not_changed
103
- @plate.should_not be_food_changed
80
+ Plate.new.should_not be_food_changed
104
81
  end
105
82
 
106
83
  it "should return true if forced dirty" do
@@ -128,13 +105,6 @@ describe 'dirty attribute tracking' do
128
105
  @plate.food = 'burger'
129
106
  @plate.should be_food_changed
130
107
  end
131
-
132
- it "should return true if array attribute changed" do
133
- couchrest_db = stub('database', :get => Plate.json_create({'_id' => '1', '_rev' => '2', 'food' => ['sushi'], JSON.create_id => 'Plate'}), :info => nil)
134
- plate = CouchPotato::Database.new(couchrest_db).load_document '1'
135
- plate.food << 'burger'
136
- plate.should be_food_changed
137
- end
138
108
 
139
109
  it "should return false if attribute not changed" do
140
110
  @plate.should_not be_food_changed
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ class Document
4
+ include CouchPotato::Persistence
5
+
6
+ property :title
7
+ property :content
8
+ end
9
+
10
+ describe "new" do
11
+ context "without arguments" do
12
+ subject { Document.new }
13
+
14
+ it { should be_a(Document) }
15
+ its(:title) { should be_nil }
16
+ its(:content) { should be_nil }
17
+ end
18
+
19
+ context "with an argument hash" do
20
+ subject { Document.new(:title => 'My Title') }
21
+
22
+ it { should be_a(Document) }
23
+ its(:title) { should == 'My Title'}
24
+ its(:content) { should be_nil }
25
+ end
26
+
27
+ context "yielding to a block" do
28
+ subject {
29
+ Document.new(:title => 'My Title') do |doc|
30
+ doc.content = 'My Content'
31
+ end
32
+ }
33
+
34
+ it { should be_a(Document) }
35
+ its(:title) { should == 'My Title'}
36
+ its(:content) { should == 'My Content'}
37
+ end
38
+ end
@@ -7,10 +7,10 @@ describe Time, 'to_json' do
7
7
  end
8
8
  end
9
9
 
10
- describe Time, 'to_s(:json)' do
10
+ describe Time, 'as_json' do
11
11
  it "should format it in the same way as to_json does so i can use this to do queries over time attributes" do
12
12
  time = Time.parse('2009-01-01 11:12:23 +0200')
13
- time.to_s(:json).should == "2009/01/01 09:12:23 +0000"
13
+ time.as_json.should == "2009/01/01 09:12:23 +0000"
14
14
  end
15
15
  end
16
16
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: davber_couch_potato
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 3
8
+ - 4
9
9
  - 0
10
- version: 0.3.0
10
+ version: 0.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - David Bergman
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-14 00:00:00 -04:00
18
+ date: 2011-02-04 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -40,13 +40,28 @@ dependencies:
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- hash: 59
43
+ hash: 21
44
44
  segments:
45
+ - 1
45
46
  - 0
46
- - 24
47
- version: "0.24"
47
+ - 1
48
+ version: 1.0.1
48
49
  type: :runtime
49
50
  version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: activemodel
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ type: :runtime
64
+ version_requirements: *id003
50
65
  description: Ruby persistence layer for CouchDB
51
66
  email: davber@gmail.com
52
67
  executables: []
@@ -111,6 +126,7 @@ files:
111
126
  - spec/fixtures/person.rb
112
127
  - spec/property_spec.rb
113
128
  - spec/rails_spec.rb
129
+ - spec/railtie_spec.rb
114
130
  - spec/spec.opts
115
131
  - spec/spec_helper.rb
116
132
  - spec/unit/active_model_compliance_spec.rb
@@ -123,6 +139,7 @@ files:
123
139
  - spec/unit/database_spec.rb
124
140
  - spec/unit/date_spec.rb
125
141
  - spec/unit/dirty_attributes_spec.rb
142
+ - spec/unit/initialize_spec.rb
126
143
  - spec/unit/json_create_id_spec.rb
127
144
  - spec/unit/lists_spec.rb
128
145
  - spec/unit/model_view_spec_spec.rb
@@ -180,6 +197,7 @@ test_files:
180
197
  - spec/fixtures/person.rb
181
198
  - spec/property_spec.rb
182
199
  - spec/rails_spec.rb
200
+ - spec/railtie_spec.rb
183
201
  - spec/spec_helper.rb
184
202
  - spec/unit/active_model_compliance_spec.rb
185
203
  - spec/unit/attributes_spec.rb
@@ -191,6 +209,7 @@ test_files:
191
209
  - spec/unit/database_spec.rb
192
210
  - spec/unit/date_spec.rb
193
211
  - spec/unit/dirty_attributes_spec.rb
212
+ - spec/unit/initialize_spec.rb
194
213
  - spec/unit/json_create_id_spec.rb
195
214
  - spec/unit/lists_spec.rb
196
215
  - spec/unit/model_view_spec_spec.rb