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