passive_record 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +27 -5
- data/lib/passive_record/associations.rb +28 -1
- data/lib/passive_record/associations/has_many.rb +4 -5
- data/lib/passive_record/associations/has_many_through.rb +14 -8
- data/lib/passive_record/associations/has_one.rb +0 -1
- data/lib/passive_record/class_methods.rb +4 -3
- data/lib/passive_record/core/query.rb +37 -24
- data/lib/passive_record/hooks.rb +11 -0
- data/lib/passive_record/instance_methods.rb +12 -5
- data/lib/passive_record/version.rb +1 -1
- data/spec/passive_record_spec.rb +38 -3
- data/spec/spec_helper.rb +8 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d9a56d9aea21fdf0049781ce8d07cf85d1e996a
|
4
|
+
data.tar.gz: 9817a28de29ec3e02268234a41562925cf04adc5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 611cd1f3b49bf74734443e19d4b5c1a95f23f598d986d9ea34f57ce5685d206440e3591417f076bbd3020979c5b36165a8518318e5ad0436bccb074bc13febee
|
7
|
+
data.tar.gz: e0a1d8f0b8a67b4eaa526336740e818ba7ceffcf09171b1ed7456402b1dcfda7761a38645c6c7480513abf53b4f0ffa8872596e0574c8823c5ca347a3fb879c7
|
data/README.md
CHANGED
@@ -104,7 +104,7 @@ PassiveRecord may be right for you!
|
|
104
104
|
- `User.find_by(conditions_hash)`
|
105
105
|
- `User.find_all_by(conditions_hash)`
|
106
106
|
- `User.all` and `User.each`
|
107
|
-
-
|
107
|
+
- `User.each` enumerates over `User.all`, giving `User.count`, `User.first`, etc.
|
108
108
|
- `User.where(conditions_hash)` (returns a `PassiveRecord::Query` object)
|
109
109
|
- `User.descendants`
|
110
110
|
- `User.destroy_all`
|
@@ -130,11 +130,11 @@ PassiveRecord may be right for you!
|
|
130
130
|
- `parent.child_id=`
|
131
131
|
- `parent.create_child(attrs)`
|
132
132
|
|
133
|
-
### Has Many
|
133
|
+
### Has Many / Has Many Through / HABTM
|
134
134
|
|
135
|
-
A model `Parent` which declares `has_many :children` will gain:
|
135
|
+
A model `Parent` which declares `has_many :children` or `has_and_belongs_to_many :children` will gain:
|
136
136
|
|
137
|
-
- `parent.children` (returns a `Relation
|
137
|
+
- `parent.children` (returns a `Relation`, documented below)
|
138
138
|
- `parent.children_ids`
|
139
139
|
- `parent.children=`
|
140
140
|
- `parent.children_ids=`
|
@@ -144,9 +144,31 @@ PassiveRecord may be right for you!
|
|
144
144
|
- `parent.children.empty?`
|
145
145
|
- `parent.children.where` (returns a `Core::Query`)
|
146
146
|
|
147
|
+
### Explicit Relations
|
148
|
+
|
149
|
+
Parent models which declare `has_many :children` gain a `parent.children` instance that returns an explicit PassiveRecord relation object, which has the following public interface:
|
150
|
+
|
151
|
+
- `parent.children.all`
|
152
|
+
- `parent.children.each` enumerates over `parent.children.all`, giving `parent.children.count`, `parent.children.first`, etc.
|
153
|
+
- `parent.children.all?(&predicate)`
|
154
|
+
- `parent.children.empty?`
|
155
|
+
- `parent.children.where(conditions)` (returns a `Core::Query`)
|
156
|
+
- `parent.children<<` (insert a new child into the relation)
|
157
|
+
|
158
|
+
### Queries
|
159
|
+
|
160
|
+
`Core::Query` objects acquired through `where` are chainable and have their own API.
|
161
|
+
|
162
|
+
- `where(conditions).all`
|
163
|
+
- `where(conditions).each` enumerates over `where(conditions).all`, so we have `where(conditions).count`, `where(conditions).first`, etc.
|
164
|
+
- `where(conditions).create(attrs)`
|
165
|
+
- `where(conditions).first_or_create(attrs)`
|
166
|
+
- `where(conditions).where(further_conditions)` (chaining)
|
167
|
+
|
147
168
|
## Hooks
|
148
169
|
|
149
|
-
- `
|
170
|
+
- `before_create :call_a_method`
|
171
|
+
- `after_create :call_another_method, :and_then_call_another_one`
|
150
172
|
- `after_update { or_use_a_block }`
|
151
173
|
|
152
174
|
## Copyright
|
@@ -15,7 +15,7 @@ module PassiveRecord
|
|
15
15
|
@associations&.map do |assn|
|
16
16
|
if assn.is_a?(HasOneAssociation) || assn.is_a?(BelongsToAssociation)
|
17
17
|
(assn.target_name_symbol.to_s + "_id").to_sym
|
18
|
-
else
|
18
|
+
else
|
19
19
|
(assn.target_name_symbol.to_s.singularize + "_ids").to_sym
|
20
20
|
end
|
21
21
|
end || []
|
@@ -50,5 +50,32 @@ module PassiveRecord
|
|
50
50
|
associate!(association)
|
51
51
|
end
|
52
52
|
end
|
53
|
+
|
54
|
+
def has_and_belongs_to_many(collection_name_sym)
|
55
|
+
habtm_join_class_name =
|
56
|
+
self.name.split('::').last.singularize +
|
57
|
+
collection_name_sym.to_s.camelize.singularize +
|
58
|
+
"JoinModel"
|
59
|
+
inverse_habtm_join_class_name =
|
60
|
+
collection_name_sym.to_s.camelize.singularize +
|
61
|
+
self.name.split('::').last.singularize +
|
62
|
+
"JoinModel"
|
63
|
+
|
64
|
+
if (Object.const_get(inverse_habtm_join_class_name) rescue false)
|
65
|
+
has_many inverse_habtm_join_class_name.underscore.pluralize.to_sym
|
66
|
+
has_many collection_name_sym, :through => inverse_habtm_join_class_name.underscore.pluralize.to_sym
|
67
|
+
else
|
68
|
+
auto_collection_sym = self.name.split('::').last.underscore.pluralize.to_sym
|
69
|
+
eval <<-ruby
|
70
|
+
class ::#{habtm_join_class_name} # class UserRoleJoinModel
|
71
|
+
include PassiveRecord # include PassiveRecord
|
72
|
+
belongs_to :#{collection_name_sym.to_s.singularize} # belongs_to :role
|
73
|
+
belongs_to :#{auto_collection_sym.to_s.singularize} # belongs_to :user
|
74
|
+
end # end
|
75
|
+
ruby
|
76
|
+
has_many habtm_join_class_name.underscore.pluralize.to_sym
|
77
|
+
has_many(collection_name_sym, :through => habtm_join_class_name.underscore.pluralize.to_sym)
|
78
|
+
end
|
79
|
+
end
|
53
80
|
end
|
54
81
|
end
|
@@ -11,7 +11,6 @@ module PassiveRecord
|
|
11
11
|
end
|
12
12
|
|
13
13
|
class HasManyRelation < HasOneRelation
|
14
|
-
include Enumerable
|
15
14
|
include Enumerable
|
16
15
|
extend Forwardable
|
17
16
|
|
@@ -28,13 +27,13 @@ module PassiveRecord
|
|
28
27
|
all.empty?
|
29
28
|
end
|
30
29
|
|
31
|
-
def where(conditions)
|
32
|
-
child_class.where(conditions.merge(parent_model_id_field => parent_model.id))
|
30
|
+
def where(conditions={})
|
31
|
+
child_class.where(conditions.merge(parent_model_id_field.to_sym => parent_model.id))
|
33
32
|
end
|
34
|
-
|
33
|
+
|
35
34
|
def <<(child)
|
36
35
|
child.send(parent_model_id_field + "=", parent_model.id)
|
37
|
-
|
36
|
+
all
|
38
37
|
end
|
39
38
|
|
40
39
|
def singular?
|
@@ -7,14 +7,11 @@ module PassiveRecord
|
|
7
7
|
end
|
8
8
|
|
9
9
|
class HasManyThroughRelation < HasManyRelation
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def results
|
17
|
-
intermediary_relation.all
|
10
|
+
def <<(child)
|
11
|
+
intermediary_relation.
|
12
|
+
where((target_sym.to_s.singularize + "_id").to_sym => child.id).
|
13
|
+
first_or_create
|
14
|
+
all
|
18
15
|
end
|
19
16
|
|
20
17
|
def all
|
@@ -30,6 +27,15 @@ module PassiveRecord
|
|
30
27
|
end
|
31
28
|
end
|
32
29
|
|
30
|
+
|
31
|
+
def intermediary_relation
|
32
|
+
association.base_association.to_relation(parent_model)
|
33
|
+
end
|
34
|
+
|
35
|
+
def results
|
36
|
+
intermediary_relation.all
|
37
|
+
end
|
38
|
+
|
33
39
|
def target_sym
|
34
40
|
name_str = association.target_name_symbol.to_s
|
35
41
|
singular_target_sym = name_str.singularize.to_sym
|
@@ -30,13 +30,10 @@ module PassiveRecord
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def find_by(conditions)
|
33
|
-
#if conditions.is_a?(Array)
|
34
|
-
# find_by_ids(conditions)
|
35
33
|
if conditions.is_a?(Hash)
|
36
34
|
where(conditions).first
|
37
35
|
else # assume we have an identifier/identifiers
|
38
36
|
find(conditions)
|
39
|
-
# find_by_id(conditions)
|
40
37
|
end
|
41
38
|
end
|
42
39
|
|
@@ -60,6 +57,10 @@ module PassiveRecord
|
|
60
57
|
|
61
58
|
register(instance)
|
62
59
|
|
60
|
+
before_create_hooks.each do |hook|
|
61
|
+
hook.run(instance)
|
62
|
+
end
|
63
|
+
|
63
64
|
attrs.each do |(k,v)|
|
64
65
|
instance.send("#{k}=", v)
|
65
66
|
end
|
@@ -1,45 +1,58 @@
|
|
1
1
|
module PassiveRecord
|
2
2
|
module Core
|
3
|
-
class Query
|
3
|
+
class Query
|
4
|
+
include Enumerable
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_accessor :klass, :conditions
|
8
|
+
|
9
|
+
def initialize(klass,conditions={})
|
10
|
+
@klass = klass
|
11
|
+
@conditions = conditions
|
12
|
+
end
|
13
|
+
|
4
14
|
def all
|
5
15
|
return [] unless conditions
|
6
|
-
|
7
|
-
klass.all.select do |instance|
|
16
|
+
klass.select do |instance|
|
8
17
|
conditions.all? do |(field,value)|
|
9
18
|
if value.is_a?(Hash)
|
10
|
-
|
11
|
-
matched = assn && value.all? do |(assn_field,val)|
|
12
|
-
if assn.is_a?(Array)
|
13
|
-
assn.any? do |assc_model|
|
14
|
-
assc_model.send(assn_field) == val
|
15
|
-
end
|
16
|
-
else
|
17
|
-
if assn.is_a?(Core::Query) || (assn.is_a?(Associations::Relation) && !assn.singular?)
|
18
|
-
assn.where(assn_field => val) # send(assn_field) == val
|
19
|
-
else
|
20
|
-
assn.send(assn_field) == val
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
matched
|
19
|
+
evaluate_nested_conditions(instance, field, value)
|
25
20
|
else
|
26
21
|
instance.send(field) == value
|
27
22
|
end
|
28
23
|
end
|
29
24
|
end
|
30
25
|
end
|
26
|
+
def_delegators :all, :each
|
31
27
|
|
32
|
-
def
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
def create
|
37
|
-
klass.create(conditions)
|
28
|
+
def create(attrs={})
|
29
|
+
klass.create(conditions.merge(attrs))
|
38
30
|
end
|
39
31
|
|
40
32
|
def first_or_create
|
41
33
|
first || create
|
42
34
|
end
|
35
|
+
|
36
|
+
def where(new_conditions={})
|
37
|
+
@conditions = new_conditions.merge(conditions)
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def ==(other_query)
|
42
|
+
@klass == other_query.klass && @conditions == other_query.conditions
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
def evaluate_nested_conditions(instance, field, value)
|
47
|
+
association = instance.send(field)
|
48
|
+
association && value.all? do |(association_field,val)|
|
49
|
+
if association.is_a?(Associations::Relation) && !association.singular?
|
50
|
+
association.where(association_field => val).any?
|
51
|
+
else
|
52
|
+
association.send(association_field) == val
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
43
56
|
end
|
44
57
|
end
|
45
58
|
end
|
data/lib/passive_record/hooks.rb
CHANGED
@@ -20,6 +20,17 @@ module PassiveRecord
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
def before_create_hooks
|
24
|
+
@hooks ||= []
|
25
|
+
@hooks.select { |hook| hook.kind == :before_create }
|
26
|
+
end
|
27
|
+
|
28
|
+
def before_create(*meth_syms, &blk)
|
29
|
+
hook = Hook.new(:before_create,*meth_syms,&blk)
|
30
|
+
@hooks ||= []
|
31
|
+
@hooks += [ hook ]
|
32
|
+
end
|
33
|
+
|
23
34
|
def after_create_hooks
|
24
35
|
@hooks ||= []
|
25
36
|
@hooks.select { |hook| hook.kind == :after_create }
|
@@ -59,7 +59,7 @@ module PassiveRecord
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def find_relation_by_target_name_symbol(meth)
|
62
|
-
relata.detect do |relation|
|
62
|
+
relata.detect do |relation|
|
63
63
|
possible_target_names(relation).include?(meth.to_s)
|
64
64
|
end
|
65
65
|
end
|
@@ -90,12 +90,19 @@ module PassiveRecord
|
|
90
90
|
end
|
91
91
|
when "#{target_name}="
|
92
92
|
if args.first.is_a?(Array)
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
if matching_relation.is_a?(Associations::HasManyThroughRelation)
|
94
|
+
intermediary = matching_relation.intermediary_relation
|
95
|
+
args.first.each do |child|
|
96
|
+
intermediary.
|
97
|
+
where((target_name.singularize + "_id").to_sym => child.id).
|
98
|
+
first_or_create
|
99
|
+
end
|
100
|
+
else
|
101
|
+
args.first.each do |child|
|
102
|
+
child.send(matching_relation.parent_model_id_field + "=", id)
|
103
|
+
end
|
96
104
|
end
|
97
105
|
else
|
98
|
-
# assume simple assignment
|
99
106
|
matching_relation.parent_model_id = args.first.id
|
100
107
|
end
|
101
108
|
when "create_#{target_name}", "create_#{target_name.singularize}"
|
data/spec/passive_record_spec.rb
CHANGED
@@ -42,11 +42,11 @@ describe "passive record models" do
|
|
42
42
|
|
43
43
|
it 'should report relations' do
|
44
44
|
dog = Dog.create
|
45
|
-
expect(dog.inspect).to eq("Family::Dog (id: #{dog.id.inspect}, created_at: #{dog.created_at}, sound: \"bark\", child_id: nil)")
|
45
|
+
expect(dog.inspect).to eq("Family::Dog (id: #{dog.id.inspect}, breed: \"#{dog.breed}\", created_at: #{dog.created_at}, sound: \"bark\", child_id: nil)")
|
46
46
|
|
47
47
|
child = Child.create
|
48
48
|
child.dogs << dog
|
49
|
-
expect(dog.inspect).to eq("Family::Dog (id: #{dog.id.inspect}, created_at: #{dog.created_at}, sound: \"bark\", child_id: #{child.id.inspect})")
|
49
|
+
expect(dog.inspect).to eq("Family::Dog (id: #{dog.id.inspect}, breed: \"#{dog.breed}\", created_at: #{dog.created_at}, sound: \"bark\", child_id: #{child.id.inspect})")
|
50
50
|
|
51
51
|
expect(child.inspect).to eq("Family::Child (id: #{child.id.inspect}, created_at: #{dog.created_at}, name: \"Alice\", toy_id: nil, dog_ids: [#{dog.id.inspect}], parent_id: nil)")
|
52
52
|
end
|
@@ -249,13 +249,33 @@ describe "passive record models" do
|
|
249
249
|
let(:child) { parent.create_child }
|
250
250
|
|
251
251
|
it 'should collect children of children' do
|
252
|
-
child.create_dog
|
252
|
+
child.create_dog(breed: 'mutt')
|
253
253
|
expect(parent.dogs.all).to all(be_a(Dog))
|
254
254
|
expect(parent.dogs.count).to eq(1)
|
255
255
|
expect(parent.dogs.first).to eq(child.dogs.first)
|
256
256
|
expect(parent.dog_ids).to eq([child.dogs.first.id])
|
257
257
|
end
|
258
258
|
|
259
|
+
it 'should chain where clauses' do
|
260
|
+
child.create_dog(breed: 'mutt')
|
261
|
+
child.create_dog(breed: 'pit')
|
262
|
+
|
263
|
+
# another mutt, not the same childs
|
264
|
+
Dog.create(breed: 'mutt')
|
265
|
+
|
266
|
+
expect(Dog.where(breed: 'mutt').count).to eq(2)
|
267
|
+
expect(child.dogs.where(breed: 'mutt').count).to eq(1)
|
268
|
+
|
269
|
+
expect(
|
270
|
+
child.dogs.
|
271
|
+
where(breed: 'mutt')
|
272
|
+
).to eq(
|
273
|
+
Dog.
|
274
|
+
where(child_id: child.id).
|
275
|
+
where(breed: 'mutt')
|
276
|
+
)
|
277
|
+
end
|
278
|
+
|
259
279
|
it 'should do the nested query example from the readme' do
|
260
280
|
child.create_dog
|
261
281
|
expect(Dog.find_all_by(child: {parent: parent})).
|
@@ -312,6 +332,21 @@ describe "passive record models" do
|
|
312
332
|
expect(user_b.friends.all).to eq([user_a])
|
313
333
|
end
|
314
334
|
end
|
335
|
+
|
336
|
+
context 'direct habtm' do
|
337
|
+
let!(:user) { User.create roles: [role] }
|
338
|
+
let(:role) { Role.create }
|
339
|
+
let(:another_user) { User.create }
|
340
|
+
|
341
|
+
it 'should manage direct habtm relations' do
|
342
|
+
expect(role.users).to include(user)
|
343
|
+
expect(user.roles).to include(role)
|
344
|
+
expect(role.user_ids).to eq([user.id])
|
345
|
+
expect(user.role_ids).to eq([role.id])
|
346
|
+
|
347
|
+
expect {role.users << another_user}.to change{role.users.count}.by(1)
|
348
|
+
end
|
349
|
+
end
|
315
350
|
end
|
316
351
|
end
|
317
352
|
|
data/spec/spec_helper.rb
CHANGED
@@ -20,8 +20,10 @@ end
|
|
20
20
|
module Family
|
21
21
|
class Dog < Model
|
22
22
|
attr_reader :sound
|
23
|
+
attr_accessor :breed
|
23
24
|
belongs_to :child
|
24
|
-
|
25
|
+
before_create { @breed = %w[pom pug].sample }
|
26
|
+
after_create { @sound = 'bark' }
|
25
27
|
end
|
26
28
|
|
27
29
|
class Toy < Model
|
@@ -78,6 +80,11 @@ end
|
|
78
80
|
class User < Model
|
79
81
|
has_many :friendships
|
80
82
|
has_many :friends, :through => :friendships
|
83
|
+
has_and_belongs_to_many :roles
|
84
|
+
end
|
85
|
+
|
86
|
+
class Role < Model
|
87
|
+
has_and_belongs_to_many :users
|
81
88
|
end
|
82
89
|
|
83
90
|
###
|