has-many-with-set 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/README.textile +20 -20
- data/lib/has-many-with-set/accessors.rb +12 -0
- data/lib/has-many-with-set/has-many-with-set.rb +14 -0
- data/lib/has-many-with-set/queries.rb +10 -2
- data/lib/has-many-with-set/relationships.rb +2 -2
- data/lib/has-many-with-set/version.rb +1 -1
- data/test/has-many-with-set_test.rb +18 -0
- data/test/tmp/db/migrate/{20121113022938_create_model_ones_model_twos_set.rb → 20121124222345_create_model_ones_model_twos_set.rb} +0 -0
- metadata +7 -6
data/CHANGELOG
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
has-many-with-set 1.1.0
|
2
|
+
|
3
|
+
- Children can now see their parents.
|
4
|
+
- The README now uses Ruby 1.9 features.
|
5
|
+
- Fixed a bug with the relationships table. By default, Rails name has_and_belongs_to_many relationship tables in alphabetical order. This breaks the save callback if your parent > child.
|
6
|
+
- Added CHANGELOG.
|
data/README.textile
CHANGED
@@ -16,11 +16,11 @@ The _join_ table model is a very redundant way of storing these relationships if
|
|
16
16
|
|
17
17
|
For example:
|
18
18
|
|
19
|
-
bc.. Tag.
|
20
|
-
Tag.
|
21
|
-
Tag.
|
22
|
-
Tag.
|
23
|
-
Tag.
|
19
|
+
bc.. Tag.create(:name => 'programming')
|
20
|
+
Tag.create(:name => 'open source')
|
21
|
+
Tag.create(:name => 'startups')
|
22
|
+
Tag.create(:name => 'ruby')
|
23
|
+
Tag.create(:name => 'development')
|
24
24
|
|
25
25
|
tags = Tag.all
|
26
26
|
|
@@ -28,12 +28,8 @@ tags = Tag.all
|
|
28
28
|
a = Article.new(:title => "Buzzword about buzzwords!",
|
29
29
|
:body => "Lorem ipsum")
|
30
30
|
|
31
|
-
rand(tags.size + 1)
|
32
|
-
t = tags[rand(tags.size)]
|
33
|
-
a.tags << t
|
34
|
-
end
|
31
|
+
a.tags = tags.sample(rand(tags.size + 1))
|
35
32
|
|
36
|
-
a.tags.uniq!
|
37
33
|
a.save
|
38
34
|
end
|
39
35
|
|
@@ -87,11 +83,11 @@ bc.. class Article < ActiveRecord::Base
|
|
87
83
|
has_many_with_set :tags # <--- key part!
|
88
84
|
end
|
89
85
|
|
90
|
-
Tag.
|
91
|
-
Tag.
|
92
|
-
Tag.
|
93
|
-
Tag.
|
94
|
-
Tag.
|
86
|
+
Tag.create(:name => 'programming')
|
87
|
+
Tag.create(:name => 'open source')
|
88
|
+
Tag.create(:name => 'startups')
|
89
|
+
Tag.create(:name => 'ruby')
|
90
|
+
Tag.create(:name => 'development')
|
95
91
|
|
96
92
|
tags = Tag.all
|
97
93
|
|
@@ -99,12 +95,8 @@ tags = Tag.all
|
|
99
95
|
a = Article.new(:title => "Buzzword about buzzwords!",
|
100
96
|
:body => "Lorem ipsum")
|
101
97
|
|
102
|
-
rand(tags.size + 1)
|
103
|
-
t = tags[rand(tags.size)]
|
104
|
-
a.tags << t
|
105
|
-
end
|
98
|
+
a.tags = tags.sample(rand(tags.size + 1))
|
106
99
|
|
107
|
-
a.tags.uniq!
|
108
100
|
a.save
|
109
101
|
end
|
110
102
|
|
@@ -119,6 +111,14 @@ Article.first.tags
|
|
119
111
|
Article.last.tags
|
120
112
|
=> [#<Tag id: 1, name: "programming", ...>, #<Tag id: 5, name: "development", ...]
|
121
113
|
|
114
|
+
# The child model can also see to which parent models it relates to
|
115
|
+
|
116
|
+
Tag.first.articles.size
|
117
|
+
=> 503
|
118
|
+
|
119
|
+
Tag.first.article.first
|
120
|
+
=> #<Article id: 2, title: "Buzzword about buzzwords!", ..>
|
121
|
+
|
122
122
|
p. Same example as before, just now using *_has_many_with_set_*. We get the impressive number of 80 rows to represent the same information that we had before with thousands of rows (roughly the same, since we use random combinations is not _exactly_ the same article/tag layout).
|
123
123
|
|
124
124
|
The funny thing in this particular example, is that since we have only five tags, there are only 32 possible ways to combine five tags together, these 32 combinations amount to 80 rows in our relationship table.... that is, even if we had a million articles we would still have the same 80 rows to represent our relationships, we don't need to create any more rows!!
|
@@ -41,5 +41,17 @@ module HasManyWithSet
|
|
41
41
|
instance_variable_set(instance_var_name, elements)
|
42
42
|
}
|
43
43
|
end
|
44
|
+
|
45
|
+
def self.build_parent_loader_method (parent_table_name, child_table_name, set_table_name, set_items_table_name)
|
46
|
+
find_query = Queries.build_find_parents_query(parent_table_name, child_table_name, set_table_name, set_items_table_name)
|
47
|
+
|
48
|
+
parent_klass = Object.const_get(parent_table_name.classify)
|
49
|
+
|
50
|
+
Proc.new {
|
51
|
+
values = []
|
52
|
+
values = parent_klass.find_by_sql([ find_query, self ]).to_a
|
53
|
+
values
|
54
|
+
}
|
55
|
+
end
|
44
56
|
end
|
45
57
|
end
|
@@ -13,14 +13,18 @@ module HasManyWithSet
|
|
13
13
|
set_model_name = set_table_name.classify
|
14
14
|
set_items_table_name = "#{ set_table_name }_#{ child_table_name }"
|
15
15
|
instance_var_name = "@#{ child_table_name }"
|
16
|
+
child_instance_var_name = "@#{ parent_table_name }"
|
16
17
|
setter_method_name = "#{ child_table_name }="
|
17
18
|
loader_method_name = "#{ set_items_table_name }_loader"
|
19
|
+
parent_loader_method_name = "#{ parent_table_name }_loader"
|
18
20
|
save_callback_method_name = "#{ set_items_table_name }_save_callback"
|
21
|
+
child_klass = Object.const_get(child_model_name)
|
19
22
|
|
20
23
|
Relationships.create_set_model(set_model_name)
|
21
24
|
Relationships.relate_child_to_set(set_model_name, child_model_name)
|
22
25
|
Relationships.relate_parent_to_set(set_model_name, parent_model_name)
|
23
26
|
|
27
|
+
# Parent methods
|
24
28
|
define_method(loader_method_name,
|
25
29
|
Accessors.build_loader_method(child_table_name, set_table_name))
|
26
30
|
|
@@ -34,6 +38,16 @@ module HasManyWithSet
|
|
34
38
|
Callbacks.build_saver_callback(set_table_name, set_items_table_name,
|
35
39
|
child_table_name, instance_var_name))
|
36
40
|
|
41
|
+
# Child methods
|
42
|
+
child_klass.send(:define_method, parent_loader_method_name,
|
43
|
+
Accessors.build_parent_loader_method(parent_table_name, child_table_name,
|
44
|
+
set_table_name, set_items_table_name))
|
45
|
+
|
46
|
+
child_klass.send(:define_method, parent_table_name,
|
47
|
+
Accessors.build_getter_method(child_instance_var_name, parent_loader_method_name))
|
48
|
+
|
49
|
+
child_klass.send(:private, parent_loader_method_name)
|
50
|
+
|
37
51
|
class_eval do
|
38
52
|
private loader_method_name
|
39
53
|
private save_callback_method_name
|
@@ -3,7 +3,7 @@ module HasManyWithSet
|
|
3
3
|
def self.build_find_empty_set_query(set_table_name, set_items_table_name)
|
4
4
|
"select #{ set_table_name }.id from #{ set_table_name }
|
5
5
|
where not exists (select null from #{ set_items_table_name }
|
6
|
-
where #{ set_items_table_name }.#{ set_table_name.singularize }_id = #{ set_table_name }.id)"
|
6
|
+
where #{ set_items_table_name }.#{ set_table_name.singularize }_id = #{ set_table_name }.id);"
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.build_find_set_query(set_table_name, set_items_table_name, child_table_name)
|
@@ -16,7 +16,15 @@ module HasManyWithSet
|
|
16
16
|
where c.#{ set_table_name.singularize }_id =
|
17
17
|
#{ set_items_table_name }.#{ set_table_name.singularize }_id) = ?
|
18
18
|
group by #{ set_table_name }.id
|
19
|
-
having count(*) =
|
19
|
+
having count(*) = ?;"
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.build_find_parents_query (parent_table_name, child_table_name, set_table_name, set_items_table_name)
|
23
|
+
"select #{ parent_table_name }.* from #{ child_table_name }
|
24
|
+
join #{ set_items_table_name } on #{ child_table_name }.id = #{ set_items_table_name }.#{ child_table_name.singularize }_id
|
25
|
+
join #{ parent_table_name } on #{ parent_table_name }.#{ set_table_name.singularize }_id =
|
26
|
+
#{ set_items_table_name }.#{ set_table_name.singularize}_id
|
27
|
+
where #{ child_table_name }.id = ?;"
|
20
28
|
end
|
21
29
|
end
|
22
30
|
end
|
@@ -8,12 +8,12 @@ module HasManyWithSet
|
|
8
8
|
def self.relate_child_to_set (set_model_name, child_model_name)
|
9
9
|
# Take the child model and add a regular many-to-many relationship to the Set model...
|
10
10
|
Object.const_get(child_model_name).class_eval do
|
11
|
-
has_and_belongs_to_many set_model_name.tableize.to_sym
|
11
|
+
has_and_belongs_to_many set_model_name.tableize.to_sym, :join_table => "#{ set_model_name.tableize }_#{ child_model_name.tableize }"
|
12
12
|
end
|
13
13
|
|
14
14
|
# ... and take the Set model and finish the many-to-many relationship.
|
15
15
|
Object.const_get(set_model_name).class_eval do
|
16
|
-
has_and_belongs_to_many child_model_name.tableize.to_sym
|
16
|
+
has_and_belongs_to_many child_model_name.tableize.to_sym, :join_table => "#{ set_model_name.tableize }_#{ child_model_name.tableize }"
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -32,6 +32,10 @@ class HasManyWithSetTest < ActiveSupport::TestCase
|
|
32
32
|
assert_respond_to ModelOne.new, "model_twos="
|
33
33
|
end
|
34
34
|
|
35
|
+
test "child class has the getter" do
|
36
|
+
assert_respond_to ModelTwo.new, "model_ones"
|
37
|
+
end
|
38
|
+
|
35
39
|
test "getter type" do
|
36
40
|
assert_kind_of Array, ModelOne.new.model_twos
|
37
41
|
end
|
@@ -111,6 +115,20 @@ class HasManyWithSetTest < ActiveSupport::TestCase
|
|
111
115
|
assert master_record.model_twos.size == items.size
|
112
116
|
end
|
113
117
|
|
118
|
+
test "children can see parents" do
|
119
|
+
item = ModelTwo.create
|
120
|
+
|
121
|
+
how_many = rand(50)
|
122
|
+
|
123
|
+
how_many.times do
|
124
|
+
record = ModelOne.new(:num => 1)
|
125
|
+
record.model_twos << item
|
126
|
+
record.save
|
127
|
+
end
|
128
|
+
|
129
|
+
assert item.model_ones.size == how_many, "#{ item.model_ones.size } != #{ how_many }"
|
130
|
+
end
|
131
|
+
|
114
132
|
test "items count match" do
|
115
133
|
ModelOne.all.each do |m|
|
116
134
|
assert m.num == m.model_twos.size
|
File without changes
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: has-many-with-set
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -62,11 +62,12 @@ files:
|
|
62
62
|
- MIT-LICENSE
|
63
63
|
- Rakefile
|
64
64
|
- README.textile
|
65
|
+
- CHANGELOG
|
65
66
|
- test/has-many-with-set_test.rb
|
66
67
|
- test/support/models.rb
|
67
68
|
- test/support/prepare_activerecord.rb
|
68
69
|
- test/test_helper.rb
|
69
|
-
- test/tmp/db/migrate/
|
70
|
+
- test/tmp/db/migrate/20121124222345_create_model_ones_model_twos_set.rb
|
70
71
|
homepage: https://github.com/ebobby/has-many-with-set
|
71
72
|
licenses: []
|
72
73
|
post_install_message:
|
@@ -81,7 +82,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
82
|
version: '0'
|
82
83
|
segments:
|
83
84
|
- 0
|
84
|
-
hash:
|
85
|
+
hash: 3966952451817173381
|
85
86
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
87
|
none: false
|
87
88
|
requirements:
|
@@ -90,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
91
|
version: '0'
|
91
92
|
segments:
|
92
93
|
- 0
|
93
|
-
hash:
|
94
|
+
hash: 3966952451817173381
|
94
95
|
requirements: []
|
95
96
|
rubyforge_project:
|
96
97
|
rubygems_version: 1.8.24
|
@@ -102,4 +103,4 @@ test_files:
|
|
102
103
|
- test/support/models.rb
|
103
104
|
- test/support/prepare_activerecord.rb
|
104
105
|
- test/test_helper.rb
|
105
|
-
- test/tmp/db/migrate/
|
106
|
+
- test/tmp/db/migrate/20121124222345_create_model_ones_model_twos_set.rb
|