mongoid_max_denormalize 0.0.1
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/LICENSE +21 -0
- data/README.md +157 -0
- data/lib/mongoid/max/denormalize/base.rb +52 -0
- data/lib/mongoid/max/denormalize/config_error.rb +15 -0
- data/lib/mongoid/max/denormalize/many_to_one.rb +218 -0
- data/lib/mongoid/max/denormalize/one_to_many.rb +86 -0
- data/lib/mongoid/max/denormalize/version.rb +13 -0
- data/lib/mongoid/max/denormalize.rb +52 -0
- data/lib/mongoid_max_denormalize.rb +11 -0
- data/spec/cases/contact_and_addresses_spec.rb +82 -0
- data/spec/cases/post_and_comments_spec.rb +158 -0
- data/spec/cases/song_and_notes_spec.rb +202 -0
- data/spec/lib/mongoid_max_denormalize_spec.rb +4 -0
- data/spec/spec_helper.rb +26 -0
- metadata +130 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) Maxime Garcia
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README.md
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# Mongoid::Max::Denormalize
|
2
|
+
|
3
|
+
`Mongoid::Max::Denormalize` is a denormalization extension for Mongoid.
|
4
|
+
|
5
|
+
It was designed for a minimum number of queries to the database.
|
6
|
+
|
7
|
+
For now, support only Mongoid 3.
|
8
|
+
|
9
|
+
* Propagate only when needed
|
10
|
+
* Take advantage of atomic operations on multi documents of MongoDB
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add the gem to your Gemfile:
|
17
|
+
|
18
|
+
gem 'mongoid_max_denormalize'
|
19
|
+
|
20
|
+
Or install with RubyGems:
|
21
|
+
|
22
|
+
$ gem install mongoid_max_denormalize
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
### Basic usage
|
29
|
+
|
30
|
+
Add `include Mongoid::Max::Denormalize` in your model and also:
|
31
|
+
|
32
|
+
denormalize relation, field_1, field_2 ... field_n, options
|
33
|
+
|
34
|
+
|
35
|
+
### One to Many
|
36
|
+
|
37
|
+
Supported fields: normal Mongoid fields, and methods.
|
38
|
+
|
39
|
+
Supported options: none.
|
40
|
+
|
41
|
+
Example :
|
42
|
+
|
43
|
+
class Post
|
44
|
+
include Mongoid::Document
|
45
|
+
field :title
|
46
|
+
def slug
|
47
|
+
title.try(:parameterize)
|
48
|
+
end
|
49
|
+
has_many :comments
|
50
|
+
end
|
51
|
+
|
52
|
+
class Comment
|
53
|
+
include Mongoid::Document
|
54
|
+
include Mongoid::Max::Denormalize
|
55
|
+
belons_to :post
|
56
|
+
denormalize :post, :title, :slug
|
57
|
+
end
|
58
|
+
|
59
|
+
@post = Post.create(:title => "Mush from the Wimp")
|
60
|
+
@comment = @post.comments.create
|
61
|
+
@comment.post_title #=> "Mush from the Wimp"
|
62
|
+
@comment.post_slug #=> "mush-from-the-wimp"
|
63
|
+
#
|
64
|
+
@post.update_attributes(:title => "All Must Share The Burden")
|
65
|
+
@comment.reload # to reload the comment from the DB
|
66
|
+
@comment.post_title #=> "All Must Share The Burden"
|
67
|
+
@comment.post_slug #=> "all-must-share-the-burden"
|
68
|
+
|
69
|
+
**Tips :** In your views, do not use `@comment.post` but `@comment.post_id` or `@comment.post_id?`
|
70
|
+
to avoid a query that checks/retrieve for the post. We want to avoid it, don't we ?
|
71
|
+
|
72
|
+
Exemple : Check your logs, you'll see queries for the post :
|
73
|
+
|
74
|
+
# app/views/comments/_comment.html.erb
|
75
|
+
<div class="comment">
|
76
|
+
<% if @comment.post %>
|
77
|
+
<%= link_to @comment.post_title, @comment.post %>
|
78
|
+
<% end %>
|
79
|
+
</div>
|
80
|
+
|
81
|
+
This is better :
|
82
|
+
|
83
|
+
# app/views/comments/_comment.html.erb
|
84
|
+
<div class="comment">
|
85
|
+
<% if @comment.post_id? %>
|
86
|
+
<%= link_to @comment.post_title, post_path(@comment.post_id, :slug => @comment.post_slug) %>
|
87
|
+
<% end %>
|
88
|
+
</div>
|
89
|
+
|
90
|
+
|
91
|
+
### Many to One
|
92
|
+
|
93
|
+
Supported fields: **only** normal Mongoid fields (no methods)
|
94
|
+
|
95
|
+
Supported options:
|
96
|
+
|
97
|
+
* `:count => true` : to keep a count !
|
98
|
+
|
99
|
+
|
100
|
+
Example :
|
101
|
+
|
102
|
+
class Post
|
103
|
+
include Mongoid::Document
|
104
|
+
include Mongoid::Max::Denormalize
|
105
|
+
has_many :comments
|
106
|
+
denormalize :comments, :rating, :stuff, :count => true
|
107
|
+
end
|
108
|
+
|
109
|
+
class Comment
|
110
|
+
include Mongoid::Document
|
111
|
+
belons_to :post
|
112
|
+
field :rating
|
113
|
+
field :stuff
|
114
|
+
end
|
115
|
+
|
116
|
+
@post = Post.create(:title => "J'accuse !")
|
117
|
+
@comment = @post.comments.create(:rating => 5, :stuff => "A")
|
118
|
+
@comment = @post.comments.create(:rating => 3, :stuff => "B")
|
119
|
+
@post.reload
|
120
|
+
@post.comments_count #=> 2
|
121
|
+
@post.comments_rating #=> [5, 3]
|
122
|
+
@post.comments_stuff #=> ["A", "B"]
|
123
|
+
|
124
|
+
You can see that each denormalized field in stored in a separate array. This is wanted.
|
125
|
+
An option `:group` will come to allow :
|
126
|
+
|
127
|
+
@post.comments_fields #=> [{:rating => 5, :stuff => "A"},{:rating => 5, :stuff => "B"}]
|
128
|
+
|
129
|
+
|
130
|
+
### Many to One
|
131
|
+
|
132
|
+
To come...
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
## Contributing
|
137
|
+
|
138
|
+
Contributions and bug reports are welcome.
|
139
|
+
|
140
|
+
Clone the repository and run `bundle install` to setup the development environment.
|
141
|
+
|
142
|
+
Provide a case spec according to your changes/needs, taking example on existing ones (in `spec/cases`).
|
143
|
+
|
144
|
+
To run the specs:
|
145
|
+
|
146
|
+
bundle exec rspec
|
147
|
+
|
148
|
+
|
149
|
+
|
150
|
+
## Credits
|
151
|
+
|
152
|
+
* Maxime Garcia [emaxime.com](http://emaxime.com) [@maximegarcia](http://twitter.com/maximegarcia)
|
153
|
+
|
154
|
+
|
155
|
+
[License](https://github.com/maximeg/mongoid_max_denormalize/blob/master/LICENSE)
|
156
|
+
\- [Report a bug](https://github.com/maximeg/mongoid_max_denormalize/issues).
|
157
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid
|
3
|
+
module Max
|
4
|
+
module Denormalize
|
5
|
+
|
6
|
+
class Base
|
7
|
+
|
8
|
+
attr_accessor :klass, :meta, :inverse_meta, :fields, :options
|
9
|
+
|
10
|
+
def initialize(klass, meta, inverse_meta, fields, options)
|
11
|
+
@klass = klass
|
12
|
+
@meta = meta
|
13
|
+
@inverse_meta = inverse_meta
|
14
|
+
@fields = fields
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def relation
|
20
|
+
@meta.name
|
21
|
+
end
|
22
|
+
def inverse_relation
|
23
|
+
@meta.inverse
|
24
|
+
end
|
25
|
+
def inverse_klass
|
26
|
+
@meta.klass
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def fields_methods
|
31
|
+
@fields_methods ||= fields.select do |field|
|
32
|
+
meta.klass.fields[field.to_s].nil?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
def fields_only
|
36
|
+
@fields_only ||= fields - fields_methods
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
|
41
|
+
def array_code_for(fields)
|
42
|
+
fields.map { |field| ":#{field}" }.join(", ")
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,218 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid
|
3
|
+
module Max
|
4
|
+
module Denormalize
|
5
|
+
|
6
|
+
class ManyToOne < Base
|
7
|
+
|
8
|
+
def attach
|
9
|
+
|
10
|
+
fields.each do |field|
|
11
|
+
klass.field "#{relation}_#{field}", type: Array
|
12
|
+
end
|
13
|
+
|
14
|
+
if has_count?
|
15
|
+
klass.field "#{relation}_count", type: Integer, default: 0
|
16
|
+
end
|
17
|
+
|
18
|
+
callback_code = <<EOM
|
19
|
+
before_save :denormalize_from_#{relation}
|
20
|
+
|
21
|
+
def denormalize_from_#{relation}
|
22
|
+
return unless #{meta.key}_changed?
|
23
|
+
|
24
|
+
fields = [#{Base.array_code_for(fields)}]
|
25
|
+
if #{meta.key}.nil?
|
26
|
+
fields.each do |field|
|
27
|
+
self.send(:"#{relation}_\#{field}=", nil)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
fields.each do |field|
|
31
|
+
self.send(:"#{relation}_\#{field}=", #{relation}.send(field))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
true
|
36
|
+
end
|
37
|
+
EOM
|
38
|
+
#klass.class_eval callback_code
|
39
|
+
|
40
|
+
callback_code = <<EOM
|
41
|
+
around_save :denormalize_to_#{inverse_relation}
|
42
|
+
|
43
|
+
def denormalize_to_#{inverse_relation}
|
44
|
+
return if !changed? && !new_record?
|
45
|
+
was_new = new_record?
|
46
|
+
was_added = false
|
47
|
+
was_removed = false
|
48
|
+
|
49
|
+
fields = [#{Base.array_code_for(fields_only)}]
|
50
|
+
|
51
|
+
remote_id = send(:#{inverse_meta.key})
|
52
|
+
|
53
|
+
to_rem = {}
|
54
|
+
to_add = {}
|
55
|
+
if #{inverse_meta.key}_changed?
|
56
|
+
changed_fields = fields
|
57
|
+
if !#{inverse_meta.key}.nil? && !#{inverse_meta.key}_was.nil?
|
58
|
+
was_added = true
|
59
|
+
changed_fields.each do |field|
|
60
|
+
to_add[:"#{relation}_\#{field}"] = send(field)
|
61
|
+
end
|
62
|
+
denormalize_to_#{inverse_relation}_old
|
63
|
+
else
|
64
|
+
if #{inverse_meta.key}_was.nil?
|
65
|
+
was_added = true
|
66
|
+
changed_fields.each do |field|
|
67
|
+
to_add[:"#{relation}_\#{field}"] = send(:"\#{field}_changed?") ? send(field) : send(:"\#{field}_was")
|
68
|
+
end
|
69
|
+
else
|
70
|
+
was_removed = true
|
71
|
+
remote_id = send(:#{inverse_meta.key}_was)
|
72
|
+
changed_fields.each do |field|
|
73
|
+
to_rem[:"#{relation}_\#{field}"] = send(:"\#{field}_changed?") ? send(:"\#{field}_was") : send(field)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
else
|
78
|
+
changed_fields = fields & changed.map(&:to_sym)
|
79
|
+
changed_fields.each do |field|
|
80
|
+
to_rem[:"#{relation}_\#{field}"] = send(:"\#{field}_was")
|
81
|
+
to_add[:"#{relation}_\#{field}"] = send(field)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
yield
|
86
|
+
return if changed_fields.empty?
|
87
|
+
|
88
|
+
to_update = { "$set" => {}, "$inc" => {} }
|
89
|
+
to_push = {}
|
90
|
+
to_get = {}
|
91
|
+
|
92
|
+
to_rem_fields = to_rem.reject {|k,v| v.nil?}.keys
|
93
|
+
to_add_fields = to_add.reject {|k,v| v.nil?}.keys
|
94
|
+
|
95
|
+
# Those to add only
|
96
|
+
(to_add_only_fields = to_add_fields - to_rem_fields).each do |field|
|
97
|
+
to_push[field] = to_add[field]
|
98
|
+
end
|
99
|
+
|
100
|
+
to_set_fields = (to_add_fields + to_rem_fields - to_add_only_fields).uniq
|
101
|
+
|
102
|
+
to_get.merge! Hash[to_set_fields.map{ |f| [f, 1] }] unless to_set_fields.empty?
|
103
|
+
|
104
|
+
obj = #{klass}.collection.find("$query" => {:_id => remote_id}, "$only" => to_get).first unless to_get.empty?
|
105
|
+
|
106
|
+
to_set_fields.each do |field|
|
107
|
+
array = obj[field.to_s] || []
|
108
|
+
|
109
|
+
if to_rem_fields.include? field
|
110
|
+
(i = array.index(to_rem[field])) and array.delete_at(i)
|
111
|
+
end
|
112
|
+
if to_add_fields.include? field
|
113
|
+
array << to_add[field]
|
114
|
+
end
|
115
|
+
|
116
|
+
to_update["$set"][field] = array
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
to_update["$inc"][:#{relation}_count] = 1 if #{has_count?} && (was_new || was_added)
|
121
|
+
to_update["$inc"][:#{relation}_count] = -1 if #{has_count?} && (was_removed)
|
122
|
+
|
123
|
+
#{klass}.collection.find(:_id => remote_id).update_all(to_update) unless to_update.empty?
|
124
|
+
|
125
|
+
|
126
|
+
#{klass}.collection.find(:_id => remote_id).update_all({"$push" => to_push}) unless to_push.empty?
|
127
|
+
end
|
128
|
+
|
129
|
+
def denormalize_to_#{inverse_relation}_old
|
130
|
+
fields = [#{Base.array_code_for(fields_only)}]
|
131
|
+
|
132
|
+
remote_id = send(:#{inverse_meta.key}_was)
|
133
|
+
|
134
|
+
to_rem = {}
|
135
|
+
fields.each do |field|
|
136
|
+
to_rem[:"#{relation}_\#{field}"] = send(:"\#{field}_was")
|
137
|
+
end
|
138
|
+
|
139
|
+
to_update = { "$set" => {}, "$inc" => {} }
|
140
|
+
to_get = {}
|
141
|
+
|
142
|
+
to_rem_fields = to_rem.reject {|k,v| v.nil?}.keys
|
143
|
+
|
144
|
+
to_set_fields = to_rem_fields
|
145
|
+
|
146
|
+
to_get.merge! Hash[to_set_fields.map{ |f| [f, 1] }] unless to_set_fields.empty?
|
147
|
+
|
148
|
+
obj = #{klass}.collection.find("$query" => {:_id => remote_id}, "$only" => to_get).first unless to_get.empty?
|
149
|
+
|
150
|
+
to_set_fields.each do |field|
|
151
|
+
array = obj[field.to_s] || []
|
152
|
+
|
153
|
+
if to_rem_fields.include? field
|
154
|
+
(i = array.index(to_rem[field])) and array.delete_at(i)
|
155
|
+
end
|
156
|
+
|
157
|
+
to_update["$set"][field] = array
|
158
|
+
end
|
159
|
+
|
160
|
+
to_update["$inc"][:#{relation}_count] = -1 if #{has_count?}
|
161
|
+
|
162
|
+
#{klass}.collection.find(:_id => remote_id).update_all(to_update) unless to_update.empty?
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
|
167
|
+
around_destroy :denormalize_to_#{inverse_relation}_destroy
|
168
|
+
|
169
|
+
def denormalize_to_#{inverse_relation}_destroy
|
170
|
+
fields = [#{Base.array_code_for(fields)}]
|
171
|
+
|
172
|
+
remote_id = send(:#{inverse_meta.key})
|
173
|
+
|
174
|
+
to_rem = {}
|
175
|
+
fields.each do |field|
|
176
|
+
to_rem[:"#{relation}_\#{field}"] = send(field)
|
177
|
+
end
|
178
|
+
|
179
|
+
yield
|
180
|
+
|
181
|
+
to_update = { "$set" => {}, "$inc" => {} }
|
182
|
+
to_push = {}
|
183
|
+
to_get = {}
|
184
|
+
|
185
|
+
to_rem_fields = to_rem.reject {|k,v| v.nil?}.keys
|
186
|
+
|
187
|
+
to_get.merge! Hash[to_rem_fields.map{ |f| [f, 1] }]
|
188
|
+
obj = #{klass}.collection.find("$query" => {:_id => remote_id}, "$only" => to_get).first unless to_get.empty?
|
189
|
+
|
190
|
+
to_rem_fields.each do |field|
|
191
|
+
array = obj[field.to_s] || []
|
192
|
+
|
193
|
+
if to_rem_fields.include? field
|
194
|
+
(i = array.index(to_rem[field])) and array.delete_at(i)
|
195
|
+
end
|
196
|
+
|
197
|
+
to_update["$set"][field] = array
|
198
|
+
end
|
199
|
+
|
200
|
+
to_update["$inc"][:#{relation}_count] = -1 if #{has_count?}
|
201
|
+
|
202
|
+
#{klass}.collection.find(:_id => remote_id).update_all(to_update) unless to_update.empty?
|
203
|
+
end
|
204
|
+
EOM
|
205
|
+
meta.klass.class_eval callback_code
|
206
|
+
puts callback_code
|
207
|
+
end
|
208
|
+
|
209
|
+
def has_count?
|
210
|
+
!options[:count].nil?
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid
|
3
|
+
module Max
|
4
|
+
module Denormalize
|
5
|
+
|
6
|
+
class OneToMany < Base
|
7
|
+
|
8
|
+
def attach
|
9
|
+
|
10
|
+
fields.each do |field|
|
11
|
+
field_meta = meta.klass.fields[field.to_s]
|
12
|
+
klass.field "#{relation}_#{field}", type: field_meta.try(:type)
|
13
|
+
end
|
14
|
+
|
15
|
+
callback_code = <<EOM
|
16
|
+
before_save :denormalize_from_#{relation}
|
17
|
+
|
18
|
+
def denormalize_from_#{relation}
|
19
|
+
return unless #{meta.key}_changed?
|
20
|
+
|
21
|
+
fields = [#{Base.array_code_for(fields)}]
|
22
|
+
if #{meta.key}.nil?
|
23
|
+
fields.each do |field|
|
24
|
+
self.send(:"#{relation}_\#{field}=", nil)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
fields.each do |field|
|
28
|
+
self.send(:"#{relation}_\#{field}=", #{relation}.send(field))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
true
|
33
|
+
end
|
34
|
+
EOM
|
35
|
+
klass.class_eval callback_code
|
36
|
+
|
37
|
+
callback_code = <<EOM
|
38
|
+
around_save :denormalize_to_#{inverse_relation}
|
39
|
+
|
40
|
+
def denormalize_to_#{inverse_relation}
|
41
|
+
return unless changed?
|
42
|
+
|
43
|
+
fields = [#{Base.array_code_for(fields)}]
|
44
|
+
fields_only = [#{Base.array_code_for(fields_only)}]
|
45
|
+
methods = [#{Base.array_code_for(fields_methods)}]
|
46
|
+
changed_fields = fields_only & changed.map(&:to_sym)
|
47
|
+
|
48
|
+
yield
|
49
|
+
|
50
|
+
return if changed_fields.count == 0
|
51
|
+
|
52
|
+
to_set = {}
|
53
|
+
(changed_fields + methods).each do |field|
|
54
|
+
to_set[:"#{relation}_\#{field}"] = send(field)
|
55
|
+
end
|
56
|
+
|
57
|
+
#{inverse_relation}.update to_set
|
58
|
+
end
|
59
|
+
|
60
|
+
around_destroy :denormalize_to_#{inverse_relation}_destroy
|
61
|
+
|
62
|
+
def denormalize_to_#{inverse_relation}_destroy
|
63
|
+
fields = [#{Base.array_code_for(fields)}]
|
64
|
+
|
65
|
+
yield
|
66
|
+
|
67
|
+
to_set = {}
|
68
|
+
fields.each do |field|
|
69
|
+
to_set[:"#{relation}_\#{field}"] = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
#{inverse_relation}.update to_set
|
73
|
+
end
|
74
|
+
EOM
|
75
|
+
meta.klass.class_eval callback_code
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid
|
3
|
+
module Max
|
4
|
+
module Denormalize
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
def denormalize(relation, *fields)
|
14
|
+
options = fields.extract_options!
|
15
|
+
|
16
|
+
#TODO make all real fields if fields.empty?
|
17
|
+
|
18
|
+
# puts "#relation : #{relation.inspect}"
|
19
|
+
# puts "#fields : #{fields.inspect}"
|
20
|
+
# puts "#options : #{options.inspect}"
|
21
|
+
|
22
|
+
meta = self.relations[relation.to_s]
|
23
|
+
raise ConfigError.new("Unknown relation :#{relation}", self) if meta.nil?
|
24
|
+
# puts "# meta : #{meta.inspect}"
|
25
|
+
|
26
|
+
inverse_meta = meta.klass.relations[meta.inverse.to_s]
|
27
|
+
raise ConfigError.new("Unknown inverse relation for :#{relation}", self) if inverse_meta.nil?
|
28
|
+
# puts "#inverse_meta : #{inverse_meta.inspect}"
|
29
|
+
|
30
|
+
methods = []
|
31
|
+
fields.each do |field|
|
32
|
+
unless meta.klass.instance_methods.include? field
|
33
|
+
raise ConfigError.new("Unknown field or method :#{field} in :#{relation}", self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
if meta.relation == Mongoid::Relations::Referenced::In && inverse_meta.relation == Mongoid::Relations::Referenced::Many
|
38
|
+
OneToMany.new(self, meta, inverse_meta, fields, options).attach
|
39
|
+
elsif meta.relation == Mongoid::Relations::Referenced::Many && inverse_meta.relation == Mongoid::Relations::Referenced::In
|
40
|
+
ManyToOne.new(self, meta, inverse_meta, fields, options).attach
|
41
|
+
else
|
42
|
+
raise ConfigError.new("Relation not supported :#{relation}", self)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'active_support/core_ext'
|
4
|
+
|
5
|
+
require 'mongoid/max/denormalize'
|
6
|
+
require 'mongoid/max/denormalize/config_error'
|
7
|
+
|
8
|
+
require 'mongoid/max/denormalize/base'
|
9
|
+
require 'mongoid/max/denormalize/one_to_many'
|
10
|
+
require 'mongoid/max/denormalize/many_to_one'
|
11
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
#
|
5
|
+
# The case
|
6
|
+
#
|
7
|
+
class Contact
|
8
|
+
include Mongoid::Document
|
9
|
+
|
10
|
+
field :name, type: String
|
11
|
+
|
12
|
+
has_many :addresses
|
13
|
+
end
|
14
|
+
|
15
|
+
class Person < Contact
|
16
|
+
end
|
17
|
+
|
18
|
+
class Address
|
19
|
+
include Mongoid::Document
|
20
|
+
include Mongoid::Max::Denormalize
|
21
|
+
|
22
|
+
belongs_to :contact
|
23
|
+
|
24
|
+
denormalize :contact, :name
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# The specs
|
29
|
+
#
|
30
|
+
describe "Case: a contact and his addresses" do
|
31
|
+
|
32
|
+
before do
|
33
|
+
@person = Person.create!(name: "John Doe")
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when nothing" do
|
37
|
+
context "considering the person" do
|
38
|
+
subject { @person }
|
39
|
+
|
40
|
+
its(:addresses) { should be_empty }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when adding a first address" do
|
45
|
+
before do
|
46
|
+
@address = @person.addresses.create!
|
47
|
+
end
|
48
|
+
|
49
|
+
context "considering the person" do
|
50
|
+
subject { @person }
|
51
|
+
|
52
|
+
its(:addresses) { should have(1).address }
|
53
|
+
end
|
54
|
+
|
55
|
+
context "considering the address" do
|
56
|
+
subject { @address }
|
57
|
+
|
58
|
+
its(:contact_name) { should eq @person.name }
|
59
|
+
|
60
|
+
context "when changing the person name" do
|
61
|
+
before do
|
62
|
+
@person.name = "John Doe Jr."
|
63
|
+
@person.save!
|
64
|
+
@address.reload
|
65
|
+
end
|
66
|
+
|
67
|
+
its(:contact_name) { should eq @person.name }
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when destroying the person" do
|
71
|
+
before do
|
72
|
+
@person.destroy
|
73
|
+
@address.reload
|
74
|
+
end
|
75
|
+
|
76
|
+
its(:contact_name) { should be_nil }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
#
|
5
|
+
# The case
|
6
|
+
#
|
7
|
+
class Post
|
8
|
+
include Mongoid::Document
|
9
|
+
|
10
|
+
field :title, type: String
|
11
|
+
|
12
|
+
def slug
|
13
|
+
title.try(:parameterize)
|
14
|
+
end
|
15
|
+
|
16
|
+
has_many :comments
|
17
|
+
end
|
18
|
+
|
19
|
+
class Comment
|
20
|
+
include Mongoid::Document
|
21
|
+
include Mongoid::Max::Denormalize
|
22
|
+
|
23
|
+
belongs_to :post
|
24
|
+
|
25
|
+
denormalize :post, :title, :slug
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# The specs
|
30
|
+
#
|
31
|
+
describe "Case: a post and his comments" do
|
32
|
+
|
33
|
+
before do
|
34
|
+
@post = Post.create!(title: "A good title !")
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when nothing" do
|
38
|
+
context "considering the post" do
|
39
|
+
subject { @post }
|
40
|
+
|
41
|
+
its(:comments) { should be_empty }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
comments_number = 2
|
46
|
+
context "when adding #{comments_number} comments" do
|
47
|
+
before do
|
48
|
+
@comments = []
|
49
|
+
comments_number.times do
|
50
|
+
@comments << @post.comments.create!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "considering the post" do
|
55
|
+
subject { @post }
|
56
|
+
|
57
|
+
its(:comments) { should have(comments_number).comment }
|
58
|
+
end
|
59
|
+
|
60
|
+
(0..comments_number-1).each do |i|
|
61
|
+
context "considering the comment #{i}" do
|
62
|
+
subject { @comments[i] }
|
63
|
+
|
64
|
+
its(:post_title) { should eq @post.title }
|
65
|
+
its(:post_slug) { should eq @post.slug }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when changing the post title" do
|
70
|
+
before do
|
71
|
+
@post.title = "A new title !"
|
72
|
+
@post.save!
|
73
|
+
@comments.each(&:reload)
|
74
|
+
end
|
75
|
+
|
76
|
+
(0..comments_number-1).each do |i|
|
77
|
+
context "considering the comment #{i}" do
|
78
|
+
subject { @comments[i] }
|
79
|
+
|
80
|
+
its(:post_title) { should eq @post.title }
|
81
|
+
its(:post_slug) { should eq @post.slug }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when destroying the post" do
|
87
|
+
before do
|
88
|
+
@post.destroy
|
89
|
+
@comments.each(&:reload)
|
90
|
+
end
|
91
|
+
|
92
|
+
(0..comments_number-1).each do |i|
|
93
|
+
context "considering the comment #{i}" do
|
94
|
+
subject { @comments[i] }
|
95
|
+
|
96
|
+
its(:post_title) { should be_nil }
|
97
|
+
its(:post_slug) { should be_nil }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "when creating a comment without associating it" do
|
104
|
+
before do
|
105
|
+
@comment_only = Comment.create!
|
106
|
+
@post.reload
|
107
|
+
end
|
108
|
+
|
109
|
+
subject { @comment_only }
|
110
|
+
|
111
|
+
its(:post_title) { should be_nil }
|
112
|
+
its(:post_slug) { should be_nil }
|
113
|
+
|
114
|
+
context "when associating it" do
|
115
|
+
before do
|
116
|
+
@comment_only.post = @post
|
117
|
+
@comment_only.save!
|
118
|
+
end
|
119
|
+
|
120
|
+
its(:post_title) { should eq @post.title }
|
121
|
+
its(:post_slug) { should eq @post.slug }
|
122
|
+
end
|
123
|
+
|
124
|
+
context "when associating it (2nd way)" do
|
125
|
+
before do
|
126
|
+
@post.comments << @comment_only
|
127
|
+
@comment_only.reload
|
128
|
+
end
|
129
|
+
|
130
|
+
its(:post_title) { should eq @post.title }
|
131
|
+
its(:post_slug) { should eq @post.slug }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "when has a comment" do
|
136
|
+
before do
|
137
|
+
@comment = @post.comments.create!
|
138
|
+
end
|
139
|
+
|
140
|
+
subject { @comment }
|
141
|
+
|
142
|
+
its(:post_title) { should eq @post.title }
|
143
|
+
its(:post_slug) { should eq @post.slug }
|
144
|
+
|
145
|
+
context "when associating the comment to another post" do
|
146
|
+
before do
|
147
|
+
@other_post = Post.create!(title: "Another title.")
|
148
|
+
@comment.post = @other_post
|
149
|
+
@comment.save
|
150
|
+
end
|
151
|
+
|
152
|
+
its(:post_title) { should eq @other_post.title }
|
153
|
+
its(:post_slug) { should eq @other_post.slug }
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
#
|
5
|
+
# The case
|
6
|
+
#
|
7
|
+
class Rating
|
8
|
+
include Mongoid::Document
|
9
|
+
|
10
|
+
field :note, type: Integer
|
11
|
+
field :comment, type: String
|
12
|
+
|
13
|
+
belongs_to :song
|
14
|
+
end
|
15
|
+
|
16
|
+
class Song
|
17
|
+
include Mongoid::Document
|
18
|
+
include Mongoid::Max::Denormalize
|
19
|
+
|
20
|
+
has_many :ratings
|
21
|
+
|
22
|
+
denormalize :ratings, :note, :comment, count: true, mean: [:note]
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
#
|
27
|
+
# The specs
|
28
|
+
#
|
29
|
+
describe "Case: a song and his ratings" do
|
30
|
+
|
31
|
+
before do
|
32
|
+
@song = Song.create!
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when nothing" do
|
36
|
+
context "considering the song" do
|
37
|
+
subject { @song }
|
38
|
+
|
39
|
+
it "should not have ratings" do
|
40
|
+
@song.ratings.should be_empty
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when adding a first rating (=5)" do
|
46
|
+
before do
|
47
|
+
@rating = @song.ratings.create!(note: 5, comment: "Good!")
|
48
|
+
@song.reload
|
49
|
+
end
|
50
|
+
|
51
|
+
context "considering the song" do
|
52
|
+
subject { @song }
|
53
|
+
|
54
|
+
its(:ratings) { should have(1).rating }
|
55
|
+
its(:ratings_count) { should eq 1 }
|
56
|
+
its(:ratings_note) { should eq [5] }
|
57
|
+
its(:ratings_comment) { should eq ["Good!"] }
|
58
|
+
|
59
|
+
# its(:ratings_mean) { should eq 5 }
|
60
|
+
|
61
|
+
context "when modifing the first rating (=4)" do
|
62
|
+
before do
|
63
|
+
@rating.note = 4
|
64
|
+
@rating.save!
|
65
|
+
@song.reload
|
66
|
+
end
|
67
|
+
|
68
|
+
its(:ratings) { should have(1).rating }
|
69
|
+
its(:ratings_count) { should eq 1 }
|
70
|
+
its(:ratings_note) { should eq [4] }
|
71
|
+
its(:ratings_comment) { should eq ["Good!"] }
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when adding an other rating (=5)" do
|
75
|
+
before do
|
76
|
+
@other_rating = @song.ratings.create!(note: 5, comment: "Another good!")
|
77
|
+
@song.reload
|
78
|
+
end
|
79
|
+
|
80
|
+
its(:ratings) { should have(2).rating }
|
81
|
+
its(:ratings_count) { should eq 2 }
|
82
|
+
its(:ratings_note) { should eq [5, 5] }
|
83
|
+
its(:ratings_comment) { should eq ["Good!", "Another good!"] }
|
84
|
+
|
85
|
+
context "when modifing the first rating (=4)" do
|
86
|
+
before do
|
87
|
+
@rating.note = 4
|
88
|
+
@rating.save!
|
89
|
+
@song.reload
|
90
|
+
end
|
91
|
+
|
92
|
+
its(:ratings_count) { should eq 2 }
|
93
|
+
its(:ratings_note) { should eq [5, 4] }
|
94
|
+
its(:ratings_comment) { should eq ["Good!", "Another good!"] }
|
95
|
+
end
|
96
|
+
|
97
|
+
context "when modifing the other rating (=4)" do
|
98
|
+
before do
|
99
|
+
@other_rating.note = 4
|
100
|
+
@other_rating.save!
|
101
|
+
@song.reload
|
102
|
+
end
|
103
|
+
|
104
|
+
its(:ratings_count) { should eq 2 }
|
105
|
+
its(:ratings_note) { should eq [5, 4] }
|
106
|
+
its(:ratings_comment) { should eq ["Good!", "Another good!"] }
|
107
|
+
|
108
|
+
context "when modifing again the other rating (=3)" do
|
109
|
+
before do
|
110
|
+
@other_rating.note = 3
|
111
|
+
@other_rating.comment = "Another good, again!"
|
112
|
+
@other_rating.save!
|
113
|
+
@song.reload
|
114
|
+
end
|
115
|
+
|
116
|
+
its(:ratings_count) { should eq 2 }
|
117
|
+
its(:ratings_note) { should eq [5, 3] }
|
118
|
+
its(:ratings_comment) { should eq ["Good!", "Another good, again!"] }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "when destroying the other rating" do
|
123
|
+
before do
|
124
|
+
@other_rating.destroy
|
125
|
+
@song.reload
|
126
|
+
end
|
127
|
+
|
128
|
+
its(:ratings_count) { should eq 1 }
|
129
|
+
its(:ratings) { should have(1).rating }
|
130
|
+
its(:ratings_note) { should eq [5] }
|
131
|
+
its(:ratings_comment) { should eq ["Good!"] }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "when creating a rating (=1) without associating it" do
|
136
|
+
before do
|
137
|
+
@rating_only = Rating.create!(note: 1, comment: "Bad")
|
138
|
+
@song.reload
|
139
|
+
end
|
140
|
+
|
141
|
+
its(:ratings) { should have(1).rating }
|
142
|
+
its(:ratings_count) { should eq 1 }
|
143
|
+
its(:ratings_note) { should eq [5] }
|
144
|
+
its(:ratings_comment) { should eq ["Good!"] }
|
145
|
+
|
146
|
+
context "when associating it" do
|
147
|
+
before do
|
148
|
+
@rating_only.song = @song
|
149
|
+
@rating_only.save!
|
150
|
+
@song.reload
|
151
|
+
end
|
152
|
+
|
153
|
+
its(:ratings) { should have(2).rating }
|
154
|
+
its(:ratings_count) { should eq 2 }
|
155
|
+
its(:ratings_note) { should eq [5, 1] }
|
156
|
+
its(:ratings_comment) { should eq ["Good!", "Bad"] }
|
157
|
+
end
|
158
|
+
|
159
|
+
context "when associating it (2nd way)" do
|
160
|
+
before do
|
161
|
+
@song.ratings << @rating_only
|
162
|
+
@song.reload
|
163
|
+
end
|
164
|
+
|
165
|
+
its(:ratings) { should have(2).rating }
|
166
|
+
its(:ratings_count) { should eq 2 }
|
167
|
+
its(:ratings_note) { should eq [5, 1] }
|
168
|
+
its(:ratings_comment) { should eq ["Good!", "Bad"] }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context "when associating to another song" do
|
173
|
+
before do
|
174
|
+
@other_song = Song.create!
|
175
|
+
@rating.song = @other_song
|
176
|
+
@rating.save!
|
177
|
+
@song.reload
|
178
|
+
end
|
179
|
+
|
180
|
+
its(:ratings) { should have(0).rating }
|
181
|
+
its(:ratings_count) { should eq 0 }
|
182
|
+
its(:ratings_note) { should eq [] }
|
183
|
+
its(:ratings_comment) { should eq [] }
|
184
|
+
|
185
|
+
context "considering the other song" do
|
186
|
+
before do
|
187
|
+
@other_song.reload
|
188
|
+
end
|
189
|
+
|
190
|
+
subject { @other_song }
|
191
|
+
|
192
|
+
its(:ratings) { should have(1).rating }
|
193
|
+
its(:ratings_count) { should eq 1 }
|
194
|
+
its(:ratings_note) { should eq [5] }
|
195
|
+
its(:ratings_comment) { should eq ["Good!"] }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rspec'
|
4
|
+
require 'mongoid'
|
5
|
+
require 'mongoid_max_denormalize'
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
Mongoid.configure do |config|
|
9
|
+
config.connect_to('mongoid_max_denormalize_test')
|
10
|
+
end
|
11
|
+
|
12
|
+
Mongoid.logger = Logger.new("log/mongoid-test.log")
|
13
|
+
Mongoid.logger.level = Logger::DEBUG
|
14
|
+
Moped.logger = Logger.new("log/moped-test.log")
|
15
|
+
Moped.logger.level = Logger::DEBUG
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
|
19
|
+
# Drop all collections and clear the identity map before each spec.
|
20
|
+
config.before(:each) do
|
21
|
+
Mongoid.purge!
|
22
|
+
Mongoid::IdentityMap.clear
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongoid_max_denormalize
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Maxime Garcia
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mongoid
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.0.rc
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.0.0.rc
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activesupport
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '3.1'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '3.1'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.9'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.9'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: guard-rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: ! ' Mongoid::Max::Denormalize is a denormalization extension for Mongoid.
|
79
|
+
|
80
|
+
'
|
81
|
+
email:
|
82
|
+
- maxime.garcia@maxbusiness.fr
|
83
|
+
executables: []
|
84
|
+
extensions: []
|
85
|
+
extra_rdoc_files: []
|
86
|
+
files:
|
87
|
+
- README.md
|
88
|
+
- LICENSE
|
89
|
+
- lib/mongoid_max_denormalize.rb
|
90
|
+
- lib/mongoid/max/denormalize/one_to_many.rb
|
91
|
+
- lib/mongoid/max/denormalize/config_error.rb
|
92
|
+
- lib/mongoid/max/denormalize/version.rb
|
93
|
+
- lib/mongoid/max/denormalize/many_to_one.rb
|
94
|
+
- lib/mongoid/max/denormalize/base.rb
|
95
|
+
- lib/mongoid/max/denormalize.rb
|
96
|
+
- spec/lib/mongoid_max_denormalize_spec.rb
|
97
|
+
- spec/cases/contact_and_addresses_spec.rb
|
98
|
+
- spec/cases/post_and_comments_spec.rb
|
99
|
+
- spec/cases/song_and_notes_spec.rb
|
100
|
+
- spec/spec_helper.rb
|
101
|
+
homepage: http://github.com/maximeg/mongoid_max_denormalize
|
102
|
+
licenses: []
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 1.8.24
|
122
|
+
signing_key:
|
123
|
+
specification_version: 3
|
124
|
+
summary: MaxMapper, polyvalent ORM for Rails
|
125
|
+
test_files:
|
126
|
+
- spec/lib/mongoid_max_denormalize_spec.rb
|
127
|
+
- spec/cases/contact_and_addresses_spec.rb
|
128
|
+
- spec/cases/post_and_comments_spec.rb
|
129
|
+
- spec/cases/song_and_notes_spec.rb
|
130
|
+
- spec/spec_helper.rb
|