passive_record 0.2.3 → 0.3.0
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/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
|