couch_potato 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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