passive_record 0.3.0 → 0.3.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.
- 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
|
###
|