tenacity 0.1.1 → 0.2.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/.gitignore +1 -0
- data/EXTEND.rdoc +32 -22
- data/LICENSE.txt +1 -1
- data/README.rdoc +14 -16
- data/Rakefile +0 -37
- data/history.txt +21 -0
- data/lib/tenacity.rb +2 -1
- data/lib/tenacity/association.rb +82 -0
- data/lib/tenacity/associations/belongs_to.rb +9 -9
- data/lib/tenacity/associations/has_many.rb +19 -31
- data/lib/tenacity/associations/has_one.rb +7 -23
- data/lib/tenacity/class_methods.rb +136 -33
- data/lib/tenacity/instance_methods.rb +9 -13
- data/lib/tenacity/orm_ext/activerecord.rb +13 -30
- data/lib/tenacity/orm_ext/couchrest/couchrest_extended_document.rb +1 -4
- data/lib/tenacity/orm_ext/couchrest/couchrest_model.rb +1 -4
- data/lib/tenacity/orm_ext/couchrest/tenacity_class_methods.rb +8 -17
- data/lib/tenacity/orm_ext/couchrest/tenacity_instance_methods.rb +6 -6
- data/lib/tenacity/orm_ext/mongo_mapper.rb +15 -25
- data/lib/tenacity/version.rb +1 -1
- data/tenacity.gemspec +3 -3
- data/test/associations/belongs_to_test.rb +17 -1
- data/test/associations/has_many_test.rb +22 -0
- data/test/associations/has_one_test.rb +15 -0
- data/test/fixtures/active_record_car.rb +4 -0
- data/test/fixtures/active_record_engine.rb +5 -0
- data/test/fixtures/couch_rest_door.rb +10 -0
- data/test/fixtures/couch_rest_windshield.rb +10 -0
- data/test/fixtures/mongo_mapper_ash_tray.rb +8 -0
- data/test/fixtures/mongo_mapper_dashboard.rb +3 -0
- data/test/fixtures/mongo_mapper_vent.rb +8 -0
- data/test/fixtures/mongo_mapper_wheel.rb +1 -1
- data/test/helpers/active_record_test_helper.rb +34 -4
- data/test/orm_ext/activerecord_test.rb +15 -8
- data/test/orm_ext/couchrest_test.rb +15 -8
- data/test/orm_ext/mongo_mapper_test.rb +14 -8
- data/test/test_helper.rb +5 -2
- metadata +16 -11
- data/Gemfile.lock +0 -70
data/.gitignore
CHANGED
data/EXTEND.rdoc
CHANGED
@@ -1,16 +1,23 @@
|
|
1
|
-
For Tenacity to interact with
|
2
|
-
the methods listed below. Beyond that, no additional configuration
|
3
|
-
necessary. Tenacity communicates with the
|
4
|
-
as long as they have been implemented and are available on the model
|
5
|
-
Tenacity will be able to manage the object's relationships.
|
1
|
+
For Tenacity to interact with a database client, the client needs to be extended
|
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
|
5
|
+
object, Tenacity will be able to manage the object's relationships.
|
6
6
|
|
7
7
|
|
8
8
|
== A note about IDs
|
9
9
|
|
10
10
|
An ID can be an integer, a string, or an object, depending on the database
|
11
|
-
and
|
12
|
-
all IDs as strings. So, all
|
13
|
-
as input parameters, and return strings for IDs.
|
11
|
+
and database client you are using. To be as compatible as possible, Tenacity
|
12
|
+
treats all IDs as strings. So, all database client extensions should accept
|
13
|
+
strings for IDs as input parameters, and return strings for IDs.
|
14
|
+
|
15
|
+
|
16
|
+
== The Association Class
|
17
|
+
|
18
|
+
A few of the methods take an association as a parameter. This association
|
19
|
+
is an instance of the Tenacity::Association class, which can be found at
|
20
|
+
lib/tenacity/association.rb.
|
14
21
|
|
15
22
|
|
16
23
|
== Class Methods
|
@@ -35,21 +42,22 @@ and return it. If no object could be found, return nil.
|
|
35
42
|
Find all objects by the specified property name, with the specified id, and
|
36
43
|
return them in an array. If no objects could be found, return an empty array.
|
37
44
|
|
38
|
-
_t_initialize_has_many_association(
|
45
|
+
_t_initialize_has_many_association(association)
|
39
46
|
|
40
|
-
Perform any
|
41
|
-
association. This could include defining properties, or callback methods,
|
47
|
+
Perform any database client specific initialization necessary to support a has
|
48
|
+
many association. This could include defining properties, or callback methods,
|
42
49
|
on the object. This method is optional, and does not need to be defined.
|
43
50
|
|
44
|
-
_t_initialize_belongs_to_association(
|
51
|
+
_t_initialize_belongs_to_association(association)
|
45
52
|
|
46
|
-
Perform any
|
47
|
-
association. This could include defining properties, or callback
|
48
|
-
on the object. This method is optional, and does not need to be
|
53
|
+
Perform any database client specific initialization necessary to support a
|
54
|
+
belongs to association. This could include defining properties, or callback
|
55
|
+
methods, on the object. This method is optional, and does not need to be
|
56
|
+
defined.
|
49
57
|
|
50
|
-
_t_initialize_has_one_association(
|
58
|
+
_t_initialize_has_one_association(association)
|
51
59
|
|
52
|
-
Perform any
|
60
|
+
Perform any database client specific initialization necessary to support a has one
|
53
61
|
association. This could include defining properties, or callback methods,
|
54
62
|
on the object. This method is optional, and does not need to be defined.
|
55
63
|
|
@@ -61,18 +69,20 @@ on the object. This method is optional, and does not need to be defined.
|
|
61
69
|
Reload the object from the database, overwriting the objects properties with
|
62
70
|
the data fetched from the database. Return nothing.
|
63
71
|
|
64
|
-
_t_associate_many(
|
72
|
+
_t_associate_many(association, associate_ids)
|
65
73
|
|
66
74
|
Create has_many associations between this object and the objects with ids
|
67
75
|
specified in the array of associate_ids. This method could involve writing
|
68
|
-
the associate_ids into a join table, or into
|
76
|
+
the associate_ids into a join table, or into one of the object's properties.
|
77
|
+
Return nothing.
|
69
78
|
|
70
|
-
_t_get_associate_ids(
|
79
|
+
_t_get_associate_ids(association)
|
71
80
|
|
72
81
|
Get the ids of the objects associated with this object through the specified
|
73
|
-
association, and return them in an array.
|
82
|
+
association, and return them in an array. Return an empty array if there
|
83
|
+
are no associated objects.
|
74
84
|
|
75
|
-
_t_clear_associates(
|
85
|
+
_t_clear_associates(association)
|
76
86
|
|
77
87
|
Destroy the association between this object and its current associates through
|
78
88
|
the specified association. Return nothing.
|
data/LICENSE.txt
CHANGED
data/README.rdoc
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
= Tenacity
|
2
2
|
|
3
|
-
|
3
|
+
A database client independent way of managing relationships between models
|
4
4
|
backed by different databases.
|
5
5
|
|
6
6
|
It is sometimes necessary, or advantageous, to use more than one database in a
|
7
|
-
given application (polyglot persistence). However, most
|
8
|
-
inter-database relationships.
|
9
|
-
|
7
|
+
given application (polyglot persistence). However, most database clients do not
|
8
|
+
support inter-database relationships. So, writing code that manages relationships
|
9
|
+
between objects backed by different databases hasn’t been nearly as easy as
|
10
|
+
writing code to manage relationships between objects in the same database.
|
10
11
|
|
11
|
-
Tenacity aims to address this by providing
|
12
|
-
|
12
|
+
Tenacity aims to address this by providing a database client independent way
|
13
|
+
of managing relationships between models backed by different databases.
|
13
14
|
|
14
15
|
Tenacity is heavily based on ActiveRecord's associations, and aims to behave in
|
15
16
|
much the same way, supporting many of the same options.
|
@@ -79,17 +80,18 @@ much the same way, supporting many of the same options.
|
|
79
80
|
* The directories that contain your model classes must be in your load path in order for Tenacity to find them.
|
80
81
|
|
81
82
|
|
82
|
-
== Supported
|
83
|
+
== Supported Database Clients
|
83
84
|
|
84
85
|
* ActiveRecord
|
85
86
|
* MongoMapper
|
86
87
|
* CouchRest (CouchModel and ExtendedDocument)
|
87
88
|
|
88
|
-
See EXTEND.rdoc for information on
|
89
|
+
See EXTEND.rdoc for information on extending Tenacity to work with other database clients.
|
89
90
|
|
90
91
|
|
91
92
|
== Documentation
|
92
|
-
* http://rdoc.info/github/jwood/tenacity/master/
|
93
|
+
* http://rdoc.info/github/jwood/tenacity/master/Tenacity/ClassMethods - Documentation on the associations supported, and their respective options
|
94
|
+
* http://rdoc.info/github/jwood/tenacity/master/frames - Full API documentation
|
93
95
|
|
94
96
|
|
95
97
|
== Contributing to Tenacity
|
@@ -105,14 +107,10 @@ See EXTEND.rdoc for information on extendeding Tenacity to work with other ORMs.
|
|
105
107
|
|
106
108
|
== Development
|
107
109
|
|
108
|
-
*
|
110
|
+
* SQLite, MongoDB, and CouchDB must be installed and configured.
|
109
111
|
* Install the dependencies
|
110
112
|
|
111
|
-
|
112
|
-
|
113
|
-
* Setup the test databases
|
114
|
-
|
115
|
-
rake test:prepare
|
113
|
+
bundle install
|
116
114
|
|
117
115
|
* Run the tests
|
118
116
|
|
@@ -123,5 +121,5 @@ See EXTEND.rdoc for information on extendeding Tenacity to work with other ORMs.
|
|
123
121
|
|
124
122
|
== Copyright
|
125
123
|
|
126
|
-
Copyright (c)
|
124
|
+
Copyright (c) 2011 John Wood. See LICENSE.txt for further details.
|
127
125
|
|
data/Rakefile
CHANGED
@@ -43,40 +43,3 @@ end
|
|
43
43
|
desc 'Delete rcov, rdoc, and other generated files'
|
44
44
|
task :clobber => [:clobber_rcov, :clobber_rdoc]
|
45
45
|
|
46
|
-
begin
|
47
|
-
require 'test/helpers/active_record_test_helper'
|
48
|
-
namespace :test do
|
49
|
-
desc "Setup the test databases"
|
50
|
-
task :prepare do
|
51
|
-
system "mysqladmin -u root drop -f tenacity_test"
|
52
|
-
system "mysqladmin -u root create tenacity_test"
|
53
|
-
|
54
|
-
ActiveRecord::Schema.define :version => 0 do
|
55
|
-
|
56
|
-
create_table :active_record_cars, :force => true do |t|
|
57
|
-
end
|
58
|
-
|
59
|
-
create_table :active_record_climate_control_units, :force => true do |t|
|
60
|
-
t.string :mongo_mapper_dashboard_id
|
61
|
-
end
|
62
|
-
|
63
|
-
create_table :active_record_cars_mongo_mapper_wheels, :force => true do |t|
|
64
|
-
t.integer :active_record_car_id
|
65
|
-
t.string :mongo_mapper_wheel_id
|
66
|
-
end
|
67
|
-
|
68
|
-
create_table :active_record_nuts, :force => true do |t|
|
69
|
-
t.string :mongo_mapper_wheel_id
|
70
|
-
end
|
71
|
-
|
72
|
-
create_table :active_record_nuts_mongo_mapper_wheels, :force => true do |t|
|
73
|
-
t.integer :active_record_nut_id
|
74
|
-
t.string :mongo_mapper_wheel_id
|
75
|
-
end
|
76
|
-
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
rescue LoadError
|
81
|
-
# No ActiveRecord
|
82
|
-
end
|
data/history.txt
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
== 0.2.0
|
2
|
+
|
3
|
+
* Major enhancements
|
4
|
+
|
5
|
+
* Added options to override assumptions on names of classes, foreign keys,
|
6
|
+
tables, and columns.
|
7
|
+
|
8
|
+
* Minor enhancements
|
9
|
+
|
10
|
+
* Added support for the :class_name option to all associations
|
11
|
+
* Added support for the :foreign_key option to all associations
|
12
|
+
* Added support for the :foreign_keys_property option to the t_has_many association
|
13
|
+
* Added support for the :join_table option to the t_has_many association
|
14
|
+
* Added support for the :association_foreign_key option to the t_has_many association
|
15
|
+
* Added support for the :association_key option to the t_has_many association
|
16
|
+
|
17
|
+
* Bug fixes
|
18
|
+
|
19
|
+
* t_has_one association was being initialized on the wrong class in the association
|
20
|
+
* Fixed bug that causing t_has_many associations not to work with SQLite
|
21
|
+
|
1
22
|
== 0.1.1
|
2
23
|
|
3
24
|
* Bug fixes
|
data/lib/tenacity.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require File.join('active_support', 'inflector')
|
2
2
|
|
3
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'association')
|
3
4
|
require File.join(File.dirname(__FILE__), 'tenacity', 'class_methods')
|
4
5
|
require File.join(File.dirname(__FILE__), 'tenacity', 'instance_methods')
|
5
6
|
require File.join(File.dirname(__FILE__), 'tenacity', 'associations', 'belongs_to')
|
@@ -20,7 +21,7 @@ module Tenacity #:nodoc:
|
|
20
21
|
include HasOne
|
21
22
|
|
22
23
|
def self.included(model)
|
23
|
-
raise "Tenacity does not support the
|
24
|
+
raise "Tenacity does not support the database client used by #{model}" unless model.respond_to?(:_t_find)
|
24
25
|
model.extend(ClassMethods)
|
25
26
|
end
|
26
27
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Tenacity
|
2
|
+
class Association
|
3
|
+
# Type type of the association (<tt>:t_has_one</tt>, <tt>:t_has_many</tt>, or <tt>:t_belongs_to</tt>)
|
4
|
+
attr_reader :type
|
5
|
+
|
6
|
+
# The name of the association
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
# The class defining the association
|
10
|
+
attr_reader :source
|
11
|
+
|
12
|
+
# The name of the associated class
|
13
|
+
attr_reader :class_name
|
14
|
+
|
15
|
+
def initialize(type, name, source, options={})
|
16
|
+
@type = type
|
17
|
+
@name = name
|
18
|
+
@source = source
|
19
|
+
|
20
|
+
if options[:class_name]
|
21
|
+
@class_name = options[:class_name]
|
22
|
+
else
|
23
|
+
@class_name = name.to_s.singularize.camelcase
|
24
|
+
end
|
25
|
+
|
26
|
+
@foreign_key = options[:foreign_key]
|
27
|
+
@foreign_keys_property = options[:foreign_keys_property]
|
28
|
+
@join_table = options[:join_table]
|
29
|
+
@association_key = options[:association_key]
|
30
|
+
@association_foreign_key = options[:association_foreign_key]
|
31
|
+
|
32
|
+
if @foreign_keys_property
|
33
|
+
if @foreign_keys_property.to_s == ActiveSupport::Inflector.singularize(name) + "_ids"
|
34
|
+
raise "#{ActiveSupport::Inflector.singularize(name) + "_ids"} is an invalid foreign keys property name"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get the associated class
|
40
|
+
def associate_class
|
41
|
+
@clazz ||= Kernel.const_get(@class_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get the foreign key used by this association. <tt>t_has_one</tt> and
|
45
|
+
# <tt>t_has_many</tt> associations need the class of the associated object
|
46
|
+
# to be specified in order to properly determine the name of the foreign key.
|
47
|
+
def foreign_key(clazz=nil)
|
48
|
+
@foreign_key || begin
|
49
|
+
if @type == :t_belongs_to
|
50
|
+
@class_name.underscore + "_id"
|
51
|
+
elsif @type == :t_has_one || @type == :t_has_many
|
52
|
+
raise "The class of the associate must be provided in order to determine the name of the foreign key" if clazz.nil?
|
53
|
+
"#{ActiveSupport::Inflector.underscore(clazz)}_id"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get the property name used to store the foreign key
|
59
|
+
def foreign_keys_property
|
60
|
+
@foreign_keys_property || "t_" + ActiveSupport::Inflector.singularize(name) + "_ids"
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get the name of the join table used by this association
|
64
|
+
def join_table
|
65
|
+
if @join_table || @source.respond_to?(:table_name)
|
66
|
+
@join_table || (name.to_s < @source.table_name ? "#{name}_#{@source.table_name}" : "#{@source.table_name}_#{name}")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Get the name of the column in the join table that represents this object
|
71
|
+
def association_key
|
72
|
+
if @association_key || @source.respond_to?(:table_name)
|
73
|
+
@association_key || @source.table_name.singularize + '_id'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Get the name of the column in the join table that represents the associated object
|
78
|
+
def association_foreign_key
|
79
|
+
@association_foreign_key || name.to_s.singularize + '_id'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -3,23 +3,23 @@ module Tenacity
|
|
3
3
|
|
4
4
|
private
|
5
5
|
|
6
|
-
def belongs_to_associate(
|
7
|
-
associate_id = self.send(
|
8
|
-
clazz = associate_class
|
6
|
+
def belongs_to_associate(association)
|
7
|
+
associate_id = self.send(association.foreign_key)
|
8
|
+
clazz = association.associate_class
|
9
9
|
clazz._t_find(associate_id)
|
10
10
|
end
|
11
11
|
|
12
|
-
def set_belongs_to_associate(
|
13
|
-
self.send "#{
|
12
|
+
def set_belongs_to_associate(association, associate)
|
13
|
+
self.send "#{association.foreign_key}=", associate.id.to_s
|
14
14
|
end
|
15
15
|
|
16
16
|
module ClassMethods #:nodoc:
|
17
|
-
def initialize_belongs_to_association(
|
18
|
-
_t_initialize_belongs_to_association(
|
17
|
+
def initialize_belongs_to_association(association)
|
18
|
+
_t_initialize_belongs_to_association(association) if self.respond_to?(:_t_initialize_belongs_to_association)
|
19
19
|
end
|
20
20
|
|
21
|
-
def _t_stringify_belongs_to_value(record,
|
22
|
-
record.send "#{
|
21
|
+
def _t_stringify_belongs_to_value(record, association)
|
22
|
+
record.send "#{association.foreign_key}=", record.send(association.foreign_key).to_s
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -3,19 +3,19 @@ module Tenacity
|
|
3
3
|
|
4
4
|
private
|
5
5
|
|
6
|
-
def has_many_associates(
|
7
|
-
ids = _t_get_associate_ids(
|
8
|
-
clazz = associate_class
|
6
|
+
def has_many_associates(association)
|
7
|
+
ids = _t_get_associate_ids(association)
|
8
|
+
clazz = association.associate_class
|
9
9
|
clazz._t_find_bulk(ids)
|
10
10
|
end
|
11
11
|
|
12
|
-
def has_many_associate_ids(
|
13
|
-
_t_get_associate_ids(
|
12
|
+
def has_many_associate_ids(association)
|
13
|
+
_t_get_associate_ids(association)
|
14
14
|
end
|
15
15
|
|
16
|
-
def set_has_many_associate_ids(
|
17
|
-
clazz = associate_class
|
18
|
-
instance_variable_set
|
16
|
+
def set_has_many_associate_ids(association, associate_ids)
|
17
|
+
clazz = association.associate_class
|
18
|
+
instance_variable_set _t_ivar_name(association), clazz._t_find_bulk(associate_ids)
|
19
19
|
end
|
20
20
|
|
21
21
|
def save_without_callback
|
@@ -25,38 +25,34 @@ module Tenacity
|
|
25
25
|
@perform_save_associates_callback = true
|
26
26
|
end
|
27
27
|
|
28
|
-
def has_many_property_name(association_id)
|
29
|
-
self.class.has_many_property_name(association_id)
|
30
|
-
end
|
31
|
-
|
32
28
|
module ClassMethods #:nodoc:
|
33
|
-
def initialize_has_many_association(
|
34
|
-
_t_initialize_has_many_association(
|
29
|
+
def initialize_has_many_association(association)
|
30
|
+
_t_initialize_has_many_association(association) if self.respond_to?(:_t_initialize_has_many_association)
|
35
31
|
|
36
32
|
attr_accessor :perform_save_associates_callback
|
37
33
|
end
|
38
34
|
|
39
|
-
def _t_save_associates(record,
|
35
|
+
def _t_save_associates(record, association)
|
40
36
|
return if record.perform_save_associates_callback == false
|
41
37
|
|
42
|
-
_t_clear_old_associations(record,
|
38
|
+
_t_clear_old_associations(record, association)
|
43
39
|
|
44
|
-
associates = (record.instance_variable_get
|
40
|
+
associates = (record.instance_variable_get record._t_ivar_name(association)) || []
|
45
41
|
associates.each do |associate|
|
46
|
-
associate.send("#{
|
42
|
+
associate.send("#{association.foreign_key(record.class)}=", record.id.to_s)
|
47
43
|
save_associate(associate)
|
48
44
|
end
|
49
45
|
|
50
46
|
unless associates.blank?
|
51
47
|
associate_ids = associates.map { |associate| associate.id.to_s }
|
52
|
-
record._t_associate_many(
|
48
|
+
record._t_associate_many(association, associate_ids)
|
53
49
|
save_associate(record)
|
54
50
|
end
|
55
51
|
end
|
56
52
|
|
57
|
-
def _t_clear_old_associations(record,
|
58
|
-
clazz = associate_class
|
59
|
-
property_name =
|
53
|
+
def _t_clear_old_associations(record, association)
|
54
|
+
clazz = association.associate_class
|
55
|
+
property_name = association.foreign_key(record.class)
|
60
56
|
|
61
57
|
old_associates = clazz._t_find_all_by_associate(property_name, record.id.to_s)
|
62
58
|
old_associates.each do |old_associate|
|
@@ -64,18 +60,10 @@ module Tenacity
|
|
64
60
|
save_associate(old_associate)
|
65
61
|
end
|
66
62
|
|
67
|
-
record._t_clear_associates(
|
63
|
+
record._t_clear_associates(association)
|
68
64
|
save_associate(record)
|
69
65
|
end
|
70
66
|
|
71
|
-
def property_name_for_record(record)
|
72
|
-
"#{ActiveSupport::Inflector.underscore(record.class.to_s)}_id"
|
73
|
-
end
|
74
|
-
|
75
|
-
def has_many_property_name(association_id)
|
76
|
-
"t_" + ActiveSupport::Inflector.singularize(association_id) + "_ids"
|
77
|
-
end
|
78
|
-
|
79
67
|
def save_associate(associate)
|
80
68
|
associate.respond_to?(:_t_save_without_callback) ? associate._t_save_without_callback : associate.save
|
81
69
|
end
|