tenacity 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/EXTEND.rdoc +10 -3
  2. data/Gemfile +1 -2
  3. data/README.rdoc +3 -2
  4. data/Rakefile +6 -0
  5. data/history.txt +11 -0
  6. data/lib/tenacity/associate_proxy.rb +1 -1
  7. data/lib/tenacity/associates_proxy.rb +4 -4
  8. data/lib/tenacity/associations/has_many.rb +7 -8
  9. data/lib/tenacity/associations/has_one.rb +4 -4
  10. data/lib/tenacity/class_methods.rb +1 -1
  11. data/lib/tenacity/errors.rb +4 -8
  12. data/lib/tenacity/instance_methods.rb +2 -2
  13. data/lib/tenacity/orm_ext/activerecord.rb +5 -1
  14. data/lib/tenacity/orm_ext/couchrest.rb +4 -0
  15. data/lib/tenacity/orm_ext/datamapper.rb +5 -1
  16. data/lib/tenacity/orm_ext/helpers.rb +3 -3
  17. data/lib/tenacity/orm_ext/mongo_mapper.rb +4 -0
  18. data/lib/tenacity/orm_ext/mongoid.rb +4 -0
  19. data/lib/tenacity/orm_ext/ripple.rb +4 -0
  20. data/lib/tenacity/orm_ext/sequel.rb +5 -1
  21. data/lib/tenacity/orm_ext/toystore.rb +4 -0
  22. data/lib/tenacity/version.rb +1 -1
  23. data/test/associations/belongs_to_test.rb +3 -3
  24. data/test/associations/has_many_test.rb +6 -6
  25. data/test/associations/has_one_test.rb +4 -4
  26. data/test/core/classmethods_test.rb +14 -0
  27. data/test/fixtures/active_record_object_with_string_id.rb +5 -0
  28. data/test/fixtures/couch_rest_object.rb +2 -0
  29. data/test/fixtures/data_mapper_object.rb +1 -0
  30. data/test/fixtures/data_mapper_object_with_string_id.rb +6 -0
  31. data/test/fixtures/mongo_mapper_object.rb +4 -0
  32. data/test/fixtures/mongoid_object.rb +2 -0
  33. data/test/fixtures/ripple_object.rb +2 -0
  34. data/test/fixtures/sequel_object_with_string_id.rb +3 -0
  35. data/test/fixtures/toystore_object.rb +2 -0
  36. data/test/helpers/active_record_test_helper.rb +4 -0
  37. data/test/helpers/data_mapper_test_helper.rb +1 -0
  38. data/test/helpers/sequel_test_helper.rb +5 -0
  39. data/test/orm_ext/activerecord_test.rb +16 -0
  40. data/test/orm_ext/datamapper_test.rb +16 -0
  41. data/test/orm_ext/mongo_mapper_test.rb +11 -0
  42. data/test/orm_ext/mongoid_test.rb +11 -0
  43. data/test/orm_ext/ripple_test.rb +147 -134
  44. data/test/orm_ext/sequel_test.rb +16 -0
  45. data/test/orm_ext/toystore_test.rb +11 -0
  46. data/test/test_helper.rb +8 -8
  47. metadata +46 -152
data/EXTEND.rdoc CHANGED
@@ -1,7 +1,7 @@
1
1
  For Tenacity to interact with a database client, the client needs to be extended
2
2
  to support the methods listed below. Beyond that, no additional configuration
3
- or code is necessary. Tenacity communicates with the client using these methods
4
- only, so as long as they have been implemented and are available on the model
3
+ or code is necessary. Tenacity communicates with the client using only these
4
+ methods, so as long as they have been implemented and are available on the model
5
5
  object, Tenacity will be able to manage the object's relationships.
6
6
 
7
7
 
@@ -16,7 +16,7 @@ lib/tenacity/association.rb.
16
16
 
17
17
  _t_id_type
18
18
 
19
- The type to use when storing instances of this class in a database.
19
+ The type to use when storing ids from instances of this class in a database.
20
20
  Usually String or Integer.
21
21
 
22
22
  _t_find(id)
@@ -90,3 +90,10 @@ when the after the object has been deleted.
90
90
  Reload the object from the database, overwriting the objects properties with
91
91
  the data fetched from the database. Returns the instance of the class (self).
92
92
 
93
+ _t_save_if_dirty
94
+
95
+ Save the object to the database only if the object has changed. This is solely
96
+ for performance. If the client library does not support dirty tracking, this
97
+ method can simply save the object. Returns the result of the save operation
98
+ if the object was saved, or false if the object was not saved.
99
+
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
- source "http://rubygems.org"
2
- source :gemcutter
1
+ source :rubygems
3
2
 
4
3
  gemspec
data/README.rdoc CHANGED
@@ -32,7 +32,7 @@ much the same way, supporting many of the same options.
32
32
  t_belongs_to :car
33
33
  end
34
34
 
35
- class Dashboard < CouchRest::ExtendedDocument
35
+ class Dashboard < CouchRest::Model::Base
36
36
  include Tenacity
37
37
  use_database MY_COUCHDB_DATABASE
38
38
 
@@ -83,7 +83,7 @@ much the same way, supporting many of the same options.
83
83
  == Supported Database Clients
84
84
 
85
85
  * ActiveRecord
86
- * CouchRest (CouchModel and ExtendedDocument)
86
+ * CouchRest (CouchRest::Model and ExtendedDocument)
87
87
  * DataMapper
88
88
  * MongoMapper
89
89
  * Mongoid
@@ -107,6 +107,7 @@ See EXTEND.rdoc for information on extending Tenacity to work with other databas
107
107
  * Start a feature/bugfix branch
108
108
  * Commit and push until you are happy with your contribution
109
109
  * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
110
+ * Run the tests in ruby 1.8.7 and 1.9.2 on the master and active_support_2_x branches
110
111
  * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
111
112
 
112
113
 
data/Rakefile CHANGED
@@ -38,6 +38,12 @@ task :long_test do
38
38
  Rake::Task["test"].invoke
39
39
  end
40
40
 
41
+ # Usage: rake 'single_test["test/association_features/belongs_to_test.rb", "/memoize the association/"]'
42
+ desc "Run a single test"
43
+ task :single_test, [:test_file, :test_name] do |test, args|
44
+ system "ruby -I'lib:lib:test:test/fixtures' #{args['test_file']} -n #{args['test_name']}"
45
+ end
46
+
41
47
  Rcov::RcovTask.new do |test|
42
48
  test.libs << 'test' << 'test/fixtures'
43
49
  test.pattern = 'test/**/*_test.rb'
data/history.txt CHANGED
@@ -1,3 +1,14 @@
1
+ == 0.5.2
2
+
3
+ * Bug fixes
4
+
5
+ * Changed relational DB clients to evaluate the schema to determine the type
6
+ of the primary key, instead of just assuming it is an Integer
7
+
8
+ * Minor enhancements
9
+
10
+ * Performance optimizations
11
+
1
12
  == 0.5.1
2
13
 
3
14
  * Bug fixes
@@ -27,7 +27,7 @@ module Tenacity
27
27
  if @association.readonly?
28
28
  raise ReadOnlyError
29
29
  else
30
- @target.save
30
+ @target._t_save_if_dirty
31
31
  end
32
32
  end
33
33
 
@@ -19,18 +19,18 @@ module Tenacity
19
19
  end
20
20
 
21
21
  def <<(object)
22
- object.save unless @parent.id.nil?
22
+ object._t_save_if_dirty unless @parent.id.nil?
23
23
  @target << AssociateProxy.new(object, @association)
24
24
  end
25
25
 
26
26
  def push(*objects)
27
- objects.each { |object| object.save } unless @parent.id.nil?
27
+ objects.each { |object| object._t_save_if_dirty } unless @parent.id.nil?
28
28
  proxies = objects.map { |object| AssociateProxy.new(object, @association) }
29
29
  @target.push(*proxies)
30
30
  end
31
31
 
32
32
  def concat(objects)
33
- objects.each { |object| object.save } unless @parent.id.nil?
33
+ objects.each { |object| object._t_save_if_dirty } unless @parent.id.nil?
34
34
  proxies = objects.map { |object| AssociateProxy.new(object, @association) }
35
35
  @target.concat(proxies)
36
36
  end
@@ -53,7 +53,7 @@ module Tenacity
53
53
 
54
54
  def remove_associates_from_parent
55
55
  @parent._t_remove_associates(@association)
56
- @parent.save
56
+ @parent._t_save_if_dirty
57
57
  end
58
58
 
59
59
  def method_missing(method, *args)
@@ -10,13 +10,13 @@ module Tenacity
10
10
  associates = has_many_associates(association)
11
11
  unless associates.nil? || associates.empty?
12
12
  if association.dependent == :destroy
13
- associates.each { |associate| association.associate_class._t_delete([_t_serialize(associate.id)]) }
13
+ associates.each { |associate| association.associate_class._t_delete(_t_serialize(associate.id)) }
14
14
  elsif association.dependent == :delete_all
15
- associates.each { |associate| association.associate_class._t_delete([_t_serialize(associate.id)], false) }
15
+ associates.each { |associate| association.associate_class._t_delete(_t_serialize(associate.id), false) }
16
16
  elsif association.dependent == :nullify
17
17
  associates.each do |associate|
18
18
  associate.send "#{association.foreign_key(self.class)}=", nil
19
- associate.save
19
+ associate._t_save_if_dirty
20
20
  end
21
21
  elsif association.foreign_key_constraints_enabled?
22
22
  raise ObjectIdInUseError.new("Unable to delete #{self.class} with id of #{self.id} because its id is being referenced by instances of #{associates.first.class}(id: #{associates.map(&:id).join(',')})!")
@@ -60,7 +60,7 @@ module Tenacity
60
60
 
61
61
  def save_without_callback
62
62
  @perform_save_associates_callback = false
63
- save
63
+ _t_save_if_dirty
64
64
  ensure
65
65
  @perform_save_associates_callback = true
66
66
  end
@@ -95,16 +95,15 @@ module Tenacity
95
95
  # be fetched here, before we clear them out in the database.
96
96
  old_associates.first
97
97
 
98
- _t_clear_old_associations(record, association)
98
+ _t_clear_old_associations(record, association, old_associates)
99
99
 
100
100
  associates = (record.instance_variable_get(record._t_ivar_name(association))) || []
101
101
  establish_relationship_in_target_objects(record, association, associates)
102
102
  destroy_orphaned_associates(association, old_associates, associates)
103
103
  end
104
104
 
105
- def _t_clear_old_associations(record, association)
105
+ def _t_clear_old_associations(record, association, old_associates)
106
106
  property_name = association.foreign_key(record.class)
107
- old_associates = get_current_associates(record, association)
108
107
  old_associates.each do |old_associate|
109
108
  old_associate.send("#{property_name}=", nil)
110
109
  save_associate(old_associate)
@@ -112,7 +111,7 @@ module Tenacity
112
111
  end
113
112
 
114
113
  def save_associate(associate)
115
- associate.respond_to?(:_t_save_without_callback) ? associate._t_save_without_callback : associate.save
114
+ associate.respond_to?(:_t_save_without_callback) ? associate._t_save_without_callback : associate._t_save_if_dirty
116
115
  end
117
116
 
118
117
  def get_current_associates(record, association)
@@ -6,12 +6,12 @@ module Tenacity
6
6
  associate = has_one_associate(association)
7
7
  unless associate.nil?
8
8
  if association.dependent == :destroy
9
- association.associate_class._t_delete([_t_serialize(associate.id)])
9
+ association.associate_class._t_delete(_t_serialize(associate.id))
10
10
  elsif association.dependent == :delete
11
- association.associate_class._t_delete([_t_serialize(associate.id)], false)
11
+ association.associate_class._t_delete(_t_serialize(associate.id), false)
12
12
  elsif association.dependent == :nullify
13
13
  associate.send "#{association.foreign_key(self.class)}=", nil
14
- associate.save
14
+ associate._t_save_if_dirty
15
15
  elsif association.foreign_key_constraints_enabled?
16
16
  raise ObjectIdInUseError.new("Unable to delete #{self.class} with id of #{self.id} because its id is being referenced by an instance of #{associate.class}(id: #{associate.id})!")
17
17
  end
@@ -28,7 +28,7 @@ module Tenacity
28
28
  def set_has_one_associate(association, associate)
29
29
  associate.send "#{association.foreign_key(self.class)}=", _t_serialize(self.id, association)
30
30
  associate.send "#{association.polymorphic_type}=", self.class.to_s if association.polymorphic?
31
- associate.save unless association.autosave == false
31
+ associate._t_save_if_dirty unless association.autosave == false
32
32
  associate
33
33
  end
34
34
 
@@ -417,7 +417,7 @@ module Tenacity
417
417
  object_id.to_s
418
418
  elsif object_id.nil?
419
419
  nil
420
- elsif [Fixnum].include?(object_id.class)
420
+ elsif [Fixnum, Integer].include?(object_id.class)
421
421
  object_id
422
422
  else
423
423
  object_id.to_s
@@ -1,17 +1,13 @@
1
1
  module Tenacity
2
2
  # Generic Tenacity exception class.
3
- class TenacityError < StandardError
4
- end
3
+ class TenacityError < StandardError; end
5
4
 
6
5
  # Raised on attempt to update an associate that is instantiated as read only.
7
- class ReadOnlyError < TenacityError
8
- end
6
+ class ReadOnlyError < TenacityError; end
9
7
 
10
8
  # Raised when one of the objects specified in the relationship does not exist in the database
11
- class ObjectDoesNotExistError < TenacityError
12
- end
9
+ class ObjectDoesNotExistError < TenacityError; end
13
10
 
14
11
  # Rasied when an attempt is made to delete an object whose id is in use by an association
15
- class ObjectIdInUseError < TenacityError
16
- end
12
+ class ObjectIdInUseError < TenacityError; end
17
13
  end
@@ -35,7 +35,7 @@ module Tenacity
35
35
  private
36
36
 
37
37
  def autosave_save_or_destroy(associate)
38
- associate.marked_for_destruction? ? autosave_destroy(associate) : associate.save
38
+ associate.marked_for_destruction? ? autosave_destroy(associate) : associate._t_save_if_dirty
39
39
  end
40
40
 
41
41
  def autosave_destroy(associate)
@@ -48,7 +48,7 @@ module Tenacity
48
48
  has_one_associate = associate.has_one_associate(association)
49
49
  if has_one_associate
50
50
  has_one_associate.send "#{association.foreign_key(associate.class)}=", nil
51
- has_one_associate.save
51
+ has_one_associate._t_save_if_dirty
52
52
  end
53
53
  end
54
54
  end
@@ -49,7 +49,7 @@ module Tenacity
49
49
  include Tenacity::OrmExt::Helpers
50
50
 
51
51
  def _t_id_type
52
- Integer
52
+ @_t_id_type_clazz ||= Kernel.const_get(columns.find{ |x| x.primary }.type.to_s.capitalize)
53
53
  end
54
54
 
55
55
  def _t_find(id)
@@ -107,6 +107,10 @@ module Tenacity
107
107
  reload
108
108
  self
109
109
  end
110
+
111
+ def _t_save_if_dirty
112
+ changed? ? save : false
113
+ end
110
114
  end
111
115
 
112
116
  end
@@ -144,6 +144,10 @@ module Tenacity
144
144
  end
145
145
  self
146
146
  end
147
+
148
+ def _t_save_if_dirty
149
+ save
150
+ end
147
151
  end
148
152
 
149
153
  end
@@ -49,7 +49,7 @@ module Tenacity
49
49
  include Tenacity::OrmExt::Helpers
50
50
 
51
51
  def _t_id_type
52
- Integer
52
+ @_t_id_type_clazz ||= properties.find{ |x| x.key? }.primitive
53
53
  end
54
54
 
55
55
  def _t_find(id)
@@ -134,6 +134,10 @@ module Tenacity
134
134
  self.class._t_find(self.id)
135
135
  end
136
136
 
137
+ def _t_save_if_dirty
138
+ dirty? ? save : false
139
+ end
140
+
137
141
  private
138
142
 
139
143
  def taint!
@@ -5,10 +5,10 @@ module Tenacity
5
5
  def id_class_for(association)
6
6
  if association.polymorphic?
7
7
  String
8
- elsif association.type == :belongs_to
9
- association.source._t_id_type
10
- else
8
+ elsif association.type == :t_belongs_to
11
9
  association.associate_class._t_id_type
10
+ else
11
+ raise "Unable to determine id class for type: #{association.type}, source: #{association.source}, target: #{association.associate_class}"
12
12
  end
13
13
  end
14
14
 
@@ -110,6 +110,10 @@ module Tenacity
110
110
  end
111
111
  self
112
112
  end
113
+
114
+ def _t_save_if_dirty
115
+ changed? ? save : false
116
+ end
113
117
  end
114
118
 
115
119
  end
@@ -112,6 +112,10 @@ module Tenacity
112
112
  reload
113
113
  self
114
114
  end
115
+
116
+ def _t_save_if_dirty
117
+ changed? ? save : false
118
+ end
115
119
  end
116
120
 
117
121
  end
@@ -139,6 +139,10 @@ module Tenacity
139
139
  self
140
140
  end
141
141
 
142
+ def _t_save_if_dirty
143
+ changed? ? save : false
144
+ end
145
+
142
146
  def save
143
147
  before_save
144
148
  super
@@ -55,7 +55,7 @@ module Tenacity
55
55
  attr_accessor :_t_belongs_to_associations
56
56
 
57
57
  def _t_id_type
58
- Integer
58
+ @_t_id_type_clazz ||= Kernel.const_get(db_schema.values.find{ |x| x[:primary_key] == true }[:type].to_s.capitalize)
59
59
  end
60
60
 
61
61
  def _t_find(id)
@@ -142,6 +142,10 @@ module Tenacity
142
142
  reload
143
143
  self
144
144
  end
145
+
146
+ def _t_save_if_dirty
147
+ !changed_columns.empty? ? save : false
148
+ end
145
149
  end
146
150
 
147
151
  end
@@ -108,6 +108,10 @@ module Tenacity
108
108
  reload
109
109
  self
110
110
  end
111
+
112
+ def _t_save_if_dirty
113
+ changed? ? save : false
114
+ end
111
115
  end
112
116
  end
113
117
  end
@@ -1,3 +1,3 @@
1
1
  module Tenacity
2
- VERSION = "0.5.1"
2
+ VERSION = "0.5.2"
3
3
  end
@@ -9,11 +9,11 @@ class BelongsToTest < Test::Unit::TestCase
9
9
 
10
10
  @source_class = class_for_extension(source)
11
11
  @source = @source_class.create({})
12
- @target_class = class_for_extension(target, :belongs_to)
12
+ @target_class = class_for_extension(target, :t_belongs_to)
13
13
  @target = @target_class.create({})
14
14
 
15
- @foreign_key = foreign_key_for(source, :belongs_to)
16
- @foreign_key_id = foreign_key_id_for(source, :belongs_to)
15
+ @foreign_key = foreign_key_for(source, :t_belongs_to)
16
+ @foreign_key_id = foreign_key_id_for(source, :t_belongs_to)
17
17
  end
18
18
 
19
19
  should "be able to fetch the id of the associated object" do
@@ -9,13 +9,13 @@ class HasManyTest < Test::Unit::TestCase
9
9
 
10
10
  @source_class = class_for_extension(source)
11
11
  @source = @source_class.create({})
12
- @target_class = class_for_extension(target, :has_many)
12
+ @target_class = class_for_extension(target, :t_has_many)
13
13
  @target_1 = @target_class.create({})
14
14
  @target_2 = @target_class.create({})
15
15
  @target_3 = @target_class.create({})
16
16
 
17
- @foreign_key = foreign_key_for(target, :has_many)
18
- @foreign_key_id = foreign_key_id_for(target, :has_many)
17
+ @foreign_key = foreign_key_for(target, :t_has_many)
18
+ @foreign_key_id = foreign_key_id_for(target, :t_has_many)
19
19
  end
20
20
 
21
21
  should "be able to set the associated objects by their ids" do
@@ -109,9 +109,9 @@ class HasManyTest < Test::Unit::TestCase
109
109
  assert_not_nil @target_class._t_find(serialize_id(@target_1))
110
110
  assert_not_nil @target_class._t_find(serialize_id(@target_2))
111
111
  assert_not_nil @target_class._t_find(serialize_id(@target_3))
112
- assert_nil @target_class._t_find(serialize_id(@target_1)).send(foreign_key_id_for(target, :belongs_to))
113
- assert_nil @target_class._t_find(serialize_id(@target_2)).send(foreign_key_id_for(target, :belongs_to))
114
- assert_nil @target_class._t_find(serialize_id(@target_3)).send(foreign_key_id_for(target, :belongs_to))
112
+ assert_nil @target_class._t_find(serialize_id(@target_1)).send(foreign_key_id_for(target, :t_belongs_to))
113
+ assert_nil @target_class._t_find(serialize_id(@target_2)).send(foreign_key_id_for(target, :t_belongs_to))
114
+ assert_nil @target_class._t_find(serialize_id(@target_3)).send(foreign_key_id_for(target, :t_belongs_to))
115
115
  end
116
116
  end
117
117