couch_potato 0.3.2 → 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,5 +1,21 @@
1
1
  ## Changes
2
2
 
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
+
3
19
  ### 0.3.0
4
20
  * support for lists (langalex)
5
21
 
data/README.md CHANGED
@@ -74,8 +74,7 @@ Then create a config/couchdb.yml:
74
74
  <<: *default
75
75
  database: <%= ENV['DB_NAME'] %>
76
76
 
77
-
78
- Alternatively you can also install Couch Potato directly as a plugin.
77
+ 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.
79
78
 
80
79
  ### Introduction
81
80
 
@@ -1,5 +1,5 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 3
4
- :patch: 2
3
+ :minor: 4
4
+ :patch: 0
5
5
  :build:
@@ -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?
@@ -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
@@ -30,7 +30,8 @@ module CouchPotato
30
30
  end
31
31
 
32
32
  # initialize a new instance of the model optionally passing it a hash of attributes.
33
- # 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.
34
35
  #
35
36
  # example:
36
37
  # class Book
@@ -38,11 +39,20 @@ module CouchPotato
38
39
  # property :title
39
40
  # end
40
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
41
48
  # book.title # => 'Time to Relax'
42
49
  def initialize(attributes = {})
43
- attributes.each do |name, value|
44
- self.send("#{name}=", value)
45
- 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?
46
56
  end
47
57
 
48
58
  # assign multiple attributes at once.
@@ -97,6 +107,14 @@ module CouchPotato
97
107
  def ==(other) #:nodoc:
98
108
  other.class == self.class && self.to_json == other.to_json
99
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
100
118
 
101
119
  def inspect
102
120
  attributes_as_string = attributes.map {|attribute, value| "#{attribute}: #{value.inspect}"}.join(", ")
@@ -4,6 +4,7 @@ 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
  end
@@ -11,14 +12,12 @@ module CouchPotato
11
12
 
12
13
  def initialize(attributes = {})
13
14
  super
14
- assign_attribute_copies_for_dirty_tracking
15
+ # assign_attribute_copies_for_dirty_tracking
15
16
  end
16
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,11 +3,15 @@ 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
+
12
+ class << self
13
+ alias_method_chain :json_create, :ghost
14
+ end
11
15
  end
12
16
  end
13
17
 
@@ -18,6 +22,6 @@ module CouchPotato
18
22
  super
19
23
  end
20
24
  end
21
-
22
25
  end
23
- 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
@@ -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
@@ -32,7 +32,7 @@ describe CouchPotato::Database, 'rails specific behavior' do
32
32
  CouchPotato.couchrest_database.save_doc(JSON.create_id => 'Autoloader::Uninitialized', '_id' => '1')
33
33
  CouchPotato.database.load('1').class.name.should == 'Autoloader::Uninitialized'
34
34
  end
35
-
35
+
36
36
  it "should load nested models" do
37
37
  CouchPotato.couchrest_database.save_doc(JSON.create_id => 'Autoloader::Nested::Nested2', '_id' => '1')
38
38
  CouchPotato.database.load('1').class.name.should == 'Autoloader::Nested::Nested2'
@@ -17,6 +17,14 @@ end
17
17
  require 'couch_potato/railtie'
18
18
 
19
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
+
20
28
  context 'yaml file contains only database names' do
21
29
  it "should set the database name from the yaml file" do
22
30
  File.stub(:read => "test: test_db")
@@ -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
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 3
8
- - 2
9
- version: 0.3.2
7
+ - 4
8
+ - 0
9
+ version: 0.4.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Alexander Lang
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-11-03 00:00:00 +01:00
17
+ date: 2010-12-10 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -135,6 +135,7 @@ files:
135
135
  - spec/unit/database_spec.rb
136
136
  - spec/unit/date_spec.rb
137
137
  - spec/unit/dirty_attributes_spec.rb
138
+ - spec/unit/initialize_spec.rb
138
139
  - spec/unit/json_create_id_spec.rb
139
140
  - spec/unit/lists_spec.rb
140
141
  - spec/unit/model_view_spec_spec.rb
@@ -202,6 +203,7 @@ test_files:
202
203
  - spec/unit/database_spec.rb
203
204
  - spec/unit/date_spec.rb
204
205
  - spec/unit/dirty_attributes_spec.rb
206
+ - spec/unit/initialize_spec.rb
205
207
  - spec/unit/json_create_id_spec.rb
206
208
  - spec/unit/lists_spec.rb
207
209
  - spec/unit/model_view_spec_spec.rb