passive_record 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/README.md +79 -12
- data/lib/passive_record/associations/belongs_to.rb +4 -0
- data/lib/passive_record/associations/has_many.rb +27 -1
- data/lib/passive_record/associations/has_many_through.rb +10 -3
- data/lib/passive_record/associations/has_one.rb +11 -2
- data/lib/passive_record/associations.rb +3 -2
- data/lib/passive_record/class_inheritable_attrs.rb +0 -1
- data/lib/passive_record/core/query.rb +6 -2
- data/lib/passive_record/instance_methods.rb +11 -5
- data/lib/passive_record/version.rb +1 -1
- data/lib/passive_record.rb +1 -0
- data/spec/passive_record_spec.rb +42 -16
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 538bfa9bf872e0e23675e06d820897253504c6c3
|
4
|
+
data.tar.gz: ece1e692df2cfd1a30d9240fc4fdfca11388f8a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 066ce8866a53c2725e9c8e642be67e0ac2dd86fbaeab3b435c354bcf6e30255b90b1082515721ed7c132246aa30a89e3d555e8611db38033f722f9e66cb075c5
|
7
|
+
data.tar.gz: 9ed65e8664ff3c288ad8dbabe17efae62b8be398ea8f1849d97ac8e72f515bcdc02bef4c4b4af10e06ddb6c6e4817bd129873e4ad743f600e33f1f784c860d85
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -31,7 +31,7 @@ PassiveRecord may be right for you!
|
|
31
31
|
- Query on attributes and associations
|
32
32
|
- Supports many-to-many and self-referential relationships
|
33
33
|
- No database required!
|
34
|
-
- Just `include PassiveRecord` to
|
34
|
+
- Just `include PassiveRecord` to get started
|
35
35
|
|
36
36
|
## Examples
|
37
37
|
|
@@ -56,32 +56,99 @@ PassiveRecord may be right for you!
|
|
56
56
|
has_many :dogs, :through => :children
|
57
57
|
end
|
58
58
|
|
59
|
-
# Let's
|
59
|
+
# Let's build some models!
|
60
60
|
parent = Parent.create
|
61
|
-
=> Parent (id: 1)
|
61
|
+
=> Parent (id: 1, child_ids: [], dog_ids: [])
|
62
62
|
|
63
63
|
child = parent.create_child
|
64
|
-
=> Child (id: 1)
|
64
|
+
=> Child (id: 1, dog_id: nil, parent_id: 1)
|
65
65
|
|
66
66
|
dog = child.create_dog
|
67
|
-
=> Dog (id: 1)
|
67
|
+
=> Dog (id: 1, child_id: 1)
|
68
68
|
|
69
|
-
#
|
69
|
+
# Inverse relationships
|
70
70
|
dog.child
|
71
|
-
=> Child (id: 1)
|
71
|
+
=> Child (id: 1, dog_id: 1, parent_id: 1)
|
72
72
|
|
73
73
|
Dog.find_by child: child
|
74
|
-
=> Dog (id: 1)
|
74
|
+
=> Dog (id: 1, child_id: 1)
|
75
75
|
|
76
|
-
#
|
76
|
+
# Has many through
|
77
77
|
parent.dogs
|
78
|
-
=> [
|
78
|
+
=> [ ...has_many :through relation... ]
|
79
79
|
|
80
|
-
|
80
|
+
parent.dogs.all
|
81
|
+
=> [Dog (id: 1, child_id: 1)]
|
82
|
+
|
83
|
+
# Nested queries
|
81
84
|
Dog.find_all_by(child: { parent: parent })
|
82
|
-
=> [Dog (id: 1)]
|
85
|
+
=> [Dog (id: 1, child_id: 1)]
|
83
86
|
````
|
84
87
|
|
88
|
+
## Interface
|
89
|
+
|
90
|
+
A class including PassiveRecord will gain the following new instance and class methods.
|
91
|
+
|
92
|
+
### Instance Methods
|
93
|
+
|
94
|
+
A class `Role` which is declared to `include PassiveRecord` will gain the following instance methods:
|
95
|
+
- `role.update(attrs_hash)`
|
96
|
+
- `role.to_h`
|
97
|
+
- We override `role.inspect` to show ID and visible attributes
|
98
|
+
|
99
|
+
### Class Methods
|
100
|
+
|
101
|
+
A class `User` which is declared to `include PassiveRecord` will gain the following class methods:
|
102
|
+
- `User.create(attrs_hash)`
|
103
|
+
- `User.find(id_or_ids)`
|
104
|
+
- `User.find_by(conditions_hash)`
|
105
|
+
- `User.find_all_by(conditions_hash)`
|
106
|
+
- `User.all` and `User.each`
|
107
|
+
- User is `Enumerable` through `User.all`, so we have `User.count`, `User.first`, etc.
|
108
|
+
- `User.where(conditions_hash)` (returns a `PassiveRecord::Query` object)
|
109
|
+
- `User.descendants`
|
110
|
+
- `User.destroy_all`
|
111
|
+
|
112
|
+
## Relationships
|
113
|
+
|
114
|
+
### Belongs To
|
115
|
+
|
116
|
+
A model `Child` which is declared `belongs_to :parent` will gain:
|
117
|
+
|
118
|
+
- `child.parent`
|
119
|
+
- `child.parent_id`
|
120
|
+
- `child.parent=`
|
121
|
+
- `child.parent_id=`
|
122
|
+
|
123
|
+
### Has One
|
124
|
+
|
125
|
+
A model `Parent` which declares `has_one :child` will gain:
|
126
|
+
|
127
|
+
- `parent.child`
|
128
|
+
- `parent.child_id`
|
129
|
+
- `parent.child=`
|
130
|
+
- `parent.child_id=`
|
131
|
+
- `parent.create_child(attrs)`
|
132
|
+
|
133
|
+
### Has Many
|
134
|
+
|
135
|
+
A model `Parent` which declares `has_many :children` will gain:
|
136
|
+
|
137
|
+
- `parent.children` (returns a `Relation`)
|
138
|
+
- `parent.children_ids`
|
139
|
+
- `parent.children=`
|
140
|
+
- `parent.children_ids=`
|
141
|
+
- `parent.create_child(attrs)`
|
142
|
+
- `parent.children<<` (insert a related model)
|
143
|
+
- `parent.children.all?(&predicate)`
|
144
|
+
- `parent.children.empty?`
|
145
|
+
- `parent.children.where` (returns a `Core::Query`)
|
146
|
+
|
147
|
+
## Hooks
|
148
|
+
|
149
|
+
- `after_create :call_method`
|
150
|
+
- `after_update { or_use_a_block }`
|
151
|
+
|
85
152
|
## Copyright
|
86
153
|
|
87
154
|
Copyright (c) 2016 Joseph Weissman
|
@@ -11,9 +11,35 @@ module PassiveRecord
|
|
11
11
|
end
|
12
12
|
|
13
13
|
class HasManyRelation < HasOneRelation
|
14
|
-
|
14
|
+
include Enumerable
|
15
|
+
include Enumerable
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
def all
|
15
19
|
child_class.where(parent_model_id_field => parent_model.id).all
|
16
20
|
end
|
21
|
+
def_delegators :all, :each
|
22
|
+
|
23
|
+
def all?(*args)
|
24
|
+
all.all?(*args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def empty?
|
28
|
+
all.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def where(conditions)
|
32
|
+
child_class.where(conditions.merge(parent_model_id_field => parent_model.id))
|
33
|
+
end
|
34
|
+
|
35
|
+
def <<(child)
|
36
|
+
child.send(parent_model_id_field + "=", parent_model.id)
|
37
|
+
lookup
|
38
|
+
end
|
39
|
+
|
40
|
+
def singular?
|
41
|
+
false
|
42
|
+
end
|
17
43
|
end
|
18
44
|
end
|
19
45
|
end
|
@@ -11,13 +11,20 @@ module PassiveRecord
|
|
11
11
|
association.base_association.to_relation(parent_model)
|
12
12
|
end
|
13
13
|
|
14
|
+
def_delegators :all, :intermedia
|
15
|
+
|
14
16
|
def results
|
15
|
-
intermediary_relation.
|
17
|
+
intermediary_relation.all
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
20
|
+
def all
|
19
21
|
if target_sym && results
|
20
|
-
results.flat_map(&target_sym)
|
22
|
+
final_results = results.flat_map(&target_sym)
|
23
|
+
if final_results.first.is_a?(Associations::Relation) && !final_results.first.singular?
|
24
|
+
final_results.first.send(:all)
|
25
|
+
else
|
26
|
+
Array(final_results)
|
27
|
+
end
|
21
28
|
else
|
22
29
|
[]
|
23
30
|
end
|
@@ -11,7 +11,13 @@ module PassiveRecord
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
class
|
14
|
+
class Relation < Struct.new(:association, :parent_model)
|
15
|
+
def singular?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class HasOneRelation < Relation
|
15
21
|
def lookup
|
16
22
|
child_class.find_by(parent_model_id_field => parent_model.id)
|
17
23
|
end
|
@@ -33,9 +39,12 @@ module PassiveRecord
|
|
33
39
|
end
|
34
40
|
|
35
41
|
def child_class
|
36
|
-
# binding.pry
|
37
42
|
Object.const_get(association.child_class_name.singularize)
|
38
43
|
end
|
44
|
+
|
45
|
+
def id
|
46
|
+
parent_model.id
|
47
|
+
end
|
39
48
|
end
|
40
49
|
end
|
41
50
|
end
|
@@ -7,7 +7,8 @@ module PassiveRecord
|
|
7
7
|
module Associations
|
8
8
|
def associate!(assn)
|
9
9
|
@associations ||= []
|
10
|
-
@associations += [assn]
|
10
|
+
@associations += [assn] unless @associations.include?(assn)
|
11
|
+
self
|
11
12
|
end
|
12
13
|
|
13
14
|
def associations_id_syms
|
@@ -15,7 +16,7 @@ module PassiveRecord
|
|
15
16
|
if assn.is_a?(HasOneAssociation) || assn.is_a?(BelongsToAssociation)
|
16
17
|
(assn.target_name_symbol.to_s + "_id").to_sym
|
17
18
|
else # plural ids
|
18
|
-
(assn.target_name_symbol.to_s + "_ids").to_sym
|
19
|
+
(assn.target_name_symbol.to_s.singularize + "_ids").to_sym
|
19
20
|
end
|
20
21
|
end || []
|
21
22
|
end
|
@@ -3,6 +3,7 @@ module PassiveRecord
|
|
3
3
|
class Query < Struct.new(:klass, :conditions)
|
4
4
|
def all
|
5
5
|
return [] unless conditions
|
6
|
+
|
6
7
|
klass.all.select do |instance|
|
7
8
|
conditions.all? do |(field,value)|
|
8
9
|
if value.is_a?(Hash)
|
@@ -13,10 +14,13 @@ module PassiveRecord
|
|
13
14
|
assc_model.send(assn_field) == val
|
14
15
|
end
|
15
16
|
else
|
16
|
-
assn.
|
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
|
17
22
|
end
|
18
23
|
end
|
19
|
-
|
20
24
|
matched
|
21
25
|
else
|
22
26
|
instance.send(field) == value
|
@@ -1,7 +1,5 @@
|
|
1
1
|
module PassiveRecord
|
2
2
|
module InstanceMethods
|
3
|
-
include PrettyPrinting
|
4
|
-
|
5
3
|
def update(attrs={})
|
6
4
|
attrs.each do |k,v|
|
7
5
|
send("#{k}=", v)
|
@@ -16,7 +14,7 @@ module PassiveRecord
|
|
16
14
|
def to_h
|
17
15
|
Hash[
|
18
16
|
attribute_names.
|
19
|
-
map do |name| [
|
17
|
+
map do |name| [
|
20
18
|
name.to_s.gsub("@","").to_sym, # key
|
21
19
|
(instance_variable_get(name) rescue send(name))] # val
|
22
20
|
end
|
@@ -85,7 +83,11 @@ module PassiveRecord
|
|
85
83
|
|
86
84
|
case meth.to_s
|
87
85
|
when target_name
|
88
|
-
matching_relation.
|
86
|
+
if matching_relation.singular?
|
87
|
+
matching_relation.lookup
|
88
|
+
else
|
89
|
+
matching_relation
|
90
|
+
end
|
89
91
|
when "#{target_name}="
|
90
92
|
if args.first.is_a?(Array)
|
91
93
|
# need to loop through each arg and set id
|
@@ -99,7 +101,11 @@ module PassiveRecord
|
|
99
101
|
when "create_#{target_name}", "create_#{target_name.singularize}"
|
100
102
|
matching_relation.create(*args)
|
101
103
|
when "#{target_name}_id"
|
102
|
-
matching_relation.
|
104
|
+
if matching_relation.is_a?(Associations::HasOneRelation)
|
105
|
+
matching_relation.lookup&.id
|
106
|
+
elsif matching_relation.is_a?(Associations::BelongsToRelation)
|
107
|
+
matching_relation.parent_model_id
|
108
|
+
end
|
103
109
|
when "#{target_name}_id="
|
104
110
|
matching_relation.parent_model_id = args.first
|
105
111
|
when "#{target_name}_ids", "#{target_name.singularize}_ids"
|
data/lib/passive_record.rb
CHANGED
data/spec/passive_record_spec.rb
CHANGED
@@ -17,6 +17,8 @@ describe PassiveRecord do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
describe "passive record models" do
|
20
|
+
before { PassiveRecord.drop_all }
|
21
|
+
|
20
22
|
context "with a simple model including PR" do
|
21
23
|
let!(:model) { SimpleModel.create(foo: value) }
|
22
24
|
let(:value) { 'foo_value' }
|
@@ -41,6 +43,12 @@ describe "passive record models" do
|
|
41
43
|
it 'should report relations' do
|
42
44
|
dog = Dog.create
|
43
45
|
expect(dog.inspect).to eq("Family::Dog (id: #{dog.id.inspect}, created_at: #{dog.created_at}, sound: \"bark\", child_id: nil)")
|
46
|
+
|
47
|
+
child = Child.create
|
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})")
|
50
|
+
|
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)")
|
44
52
|
end
|
45
53
|
end
|
46
54
|
|
@@ -132,7 +140,7 @@ describe "passive record models" do
|
|
132
140
|
let(:post) { Post.create }
|
133
141
|
let(:user) { User.create }
|
134
142
|
|
135
|
-
subject(:
|
143
|
+
subject(:posts_with_comment_by_user) do
|
136
144
|
Post.find_by comments: { user: user }
|
137
145
|
end
|
138
146
|
|
@@ -141,8 +149,8 @@ describe "passive record models" do
|
|
141
149
|
end
|
142
150
|
|
143
151
|
it 'should find a single record through a nested query' do
|
144
|
-
|
145
|
-
expect(post).to eq(
|
152
|
+
# post_again = Post.find_by comments: { user: user }
|
153
|
+
expect(post).to eq(posts_with_comment_by_user)
|
146
154
|
end
|
147
155
|
|
148
156
|
it 'should find multiple records through a nested query' do
|
@@ -150,7 +158,8 @@ describe "passive record models" do
|
|
150
158
|
another_post.create_comment(user: user)
|
151
159
|
|
152
160
|
posts = Post.find_all_by comments: { user: user }
|
153
|
-
expect(posts).to eq(
|
161
|
+
expect(posts.count).to eq(2)
|
162
|
+
# expect(posts).to eq([post,another_post])
|
154
163
|
end
|
155
164
|
end
|
156
165
|
end
|
@@ -186,9 +195,16 @@ describe "passive record models" do
|
|
186
195
|
it 'should have inverse relationships' do
|
187
196
|
toy = child.create_toy
|
188
197
|
expect(toy.child).to eq(child)
|
198
|
+
|
189
199
|
another_toy = another_child.create_toy
|
190
200
|
expect(another_toy.child).to eq(another_child)
|
191
201
|
end
|
202
|
+
|
203
|
+
it 'should assign parents' do
|
204
|
+
toy = Toy.create
|
205
|
+
toy.child = child
|
206
|
+
expect(child.toy).to eq(toy)
|
207
|
+
end
|
192
208
|
end
|
193
209
|
|
194
210
|
context 'one-to-many relationships' do
|
@@ -196,13 +212,23 @@ describe "passive record models" do
|
|
196
212
|
let(:another_parent) { Parent.create(children: [another_child]) }
|
197
213
|
let(:another_child) { Child.create }
|
198
214
|
|
199
|
-
|
200
|
-
|
201
|
-
|
215
|
+
describe "#xxx<<" do
|
216
|
+
it 'should create children with <<' do
|
217
|
+
child = Child.create
|
218
|
+
expect {parent.children << child}.to change{parent.children.count}.by(1)
|
219
|
+
expect(parent.children).to include(child)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "#create_xxx" do
|
224
|
+
it 'should create children' do
|
225
|
+
expect { parent.create_child }.to change{ Child.count }.by(1)
|
226
|
+
expect(parent.children).to all(be_a(Child))
|
227
|
+
end
|
202
228
|
end
|
203
229
|
|
204
230
|
it 'should assign children on creation' do
|
205
|
-
expect(another_parent.children).to match_array([another_child])
|
231
|
+
expect(another_parent.children.all).to match_array([another_child])
|
206
232
|
end
|
207
233
|
|
208
234
|
it 'should create inverse relationships' do
|
@@ -213,7 +239,7 @@ describe "passive record models" do
|
|
213
239
|
expect(another_child.parent).to eq(parent)
|
214
240
|
|
215
241
|
expect(child.id).not_to eq(another_child.id)
|
216
|
-
expect(parent.children).to eq([child, another_child])
|
242
|
+
expect(parent.children.all).to eq([child, another_child])
|
217
243
|
expect(parent.children_ids).to eq([child.id, another_child.id])
|
218
244
|
end
|
219
245
|
end
|
@@ -224,7 +250,7 @@ describe "passive record models" do
|
|
224
250
|
|
225
251
|
it 'should collect children of children' do
|
226
252
|
child.create_dog
|
227
|
-
expect(parent.dogs).to all(be_a(Dog))
|
253
|
+
expect(parent.dogs.all).to all(be_a(Dog))
|
228
254
|
expect(parent.dogs.count).to eq(1)
|
229
255
|
expect(parent.dogs.first).to eq(child.dogs.first)
|
230
256
|
expect(parent.dog_ids).to eq([child.dogs.first.id])
|
@@ -233,7 +259,7 @@ describe "passive record models" do
|
|
233
259
|
it 'should do the nested query example from the readme' do
|
234
260
|
child.create_dog
|
235
261
|
expect(Dog.find_all_by(child: {parent: parent})).
|
236
|
-
to eq(parent.dogs)
|
262
|
+
to eq(parent.dogs.all)
|
237
263
|
end
|
238
264
|
|
239
265
|
it 'should work for has-one intermediary relationships' do
|
@@ -253,7 +279,7 @@ describe "passive record models" do
|
|
253
279
|
post = Post.create
|
254
280
|
user = User.create
|
255
281
|
Comment.create(post: post, user: user)
|
256
|
-
expect(post.commenters).to eq([user])
|
282
|
+
expect(post.commenters.all).to eq([user])
|
257
283
|
end
|
258
284
|
end
|
259
285
|
|
@@ -266,8 +292,8 @@ describe "passive record models" do
|
|
266
292
|
expect(appointment.doctor).to eq(doctor)
|
267
293
|
expect(appointment.patient).to eq(patient)
|
268
294
|
|
269
|
-
expect(patient.doctors).to eq([doctor])
|
270
|
-
expect(doctor.patients).to eq([patient])
|
295
|
+
expect(patient.doctors.all).to eq([doctor])
|
296
|
+
expect(doctor.patients.all).to eq([patient])
|
271
297
|
end
|
272
298
|
end
|
273
299
|
|
@@ -282,8 +308,8 @@ describe "passive record models" do
|
|
282
308
|
Friendship.create(user: user_a, friend: user_b)
|
283
309
|
Friendship.create(user: user_b, friend: user_a)
|
284
310
|
|
285
|
-
expect(user_a.friends).to eq([user_b])
|
286
|
-
expect(user_b.friends).to eq([user_a])
|
311
|
+
expect(user_a.friends.all).to eq([user_b])
|
312
|
+
expect(user_b.friends.all).to eq([user_a])
|
287
313
|
end
|
288
314
|
end
|
289
315
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: passive_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joseph Weissman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|