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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 44c674c3b4dc0121e16a6368fce9d4109ccf6b51
4
- data.tar.gz: 7621a7ffbba7ee1cf08cd36bcd0cb053d36da13f
3
+ metadata.gz: 538bfa9bf872e0e23675e06d820897253504c6c3
4
+ data.tar.gz: ece1e692df2cfd1a30d9240fc4fdfca11388f8a3
5
5
  SHA512:
6
- metadata.gz: ea2328b524559ed872dbf095380ecd2d3f4b5fba455c798c600ba35e540965c42929e07c98d8c49e1ce19b456690871cb69bf0e4e3e975f72e43c0cd528af3c3
7
- data.tar.gz: 5c12ba3c4eb166561a9c93703dde48e79e0cea00712c3eab9d919591073b16615e8c0f6e905132d56c525e81a46fd6df022e5ee1aa06e1fefa2f8834c714102c
6
+ metadata.gz: 066ce8866a53c2725e9c8e642be67e0ac2dd86fbaeab3b435c354bcf6e30255b90b1082515721ed7c132246aa30a89e3d555e8611db38033f722f9e66cb075c5
7
+ data.tar.gz: 9ed65e8664ff3c288ad8dbabe17efae62b8be398ea8f1849d97ac8e72f515bcdc02bef4c4b4af10e06ddb6c6e4817bd129873e4ad743f600e33f1f784c860d85
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
1
  source 'https://rubygems.org'
2
+ ruby '2.3.0'
2
3
 
3
4
  gemspec
4
5
 
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 activate a PORO in the system
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 create some models!
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
- # inverse relationships
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
- # has many thru
76
+ # Has many through
77
77
  parent.dogs
78
- => [Dog (id: 1)]
78
+ => [ ...has_many :through relation... ]
79
79
 
80
- # nested queries
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,6 +11,10 @@ module PassiveRecord
11
11
  end
12
12
 
13
13
  class BelongsToRelation < Struct.new(:association, :child_model)
14
+ def singular?
15
+ true
16
+ end
17
+
14
18
  def lookup
15
19
  association.parent_class.find_by(parent_model_id)
16
20
  end
@@ -11,9 +11,35 @@ module PassiveRecord
11
11
  end
12
12
 
13
13
  class HasManyRelation < HasOneRelation
14
- def lookup
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.lookup
17
+ intermediary_relation.all
16
18
  end
17
19
 
18
- def lookup
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 HasOneRelation < Struct.new(:association, :parent_model)
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
@@ -14,7 +14,6 @@ module ClassLevelInheritableAttributes
14
14
  )
15
15
  end
16
16
 
17
- # binding.pry
18
17
  @inheritable_attributes
19
18
  end
20
19
 
@@ -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.send(assn_field) == val
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.lookup
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.parent_model_id
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"
@@ -1,4 +1,4 @@
1
1
  module PassiveRecord
2
2
  # passive_record version
3
- VERSION = "0.2.3"
3
+ VERSION = "0.3.0"
4
4
  end
@@ -21,6 +21,7 @@ module PassiveRecord
21
21
  def self.included(base)
22
22
  base.send :include, InstanceMethods
23
23
  base.send :include, ClassLevelInheritableAttributes
24
+ base.send :include, PrettyPrinting
24
25
 
25
26
  base.class_eval do
26
27
  inheritable_attrs :hooks, :associations
@@ -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(:posts_with_comments_by_user) do
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
- post = Post.find_by comments: { user: user }
145
- expect(post).to eq(post)
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([post,another_post])
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
- it 'should create children' do
200
- expect { parent.create_child }.to change{ Child.count }.by(1)
201
- expect(parent.children).to all(be_a(Child))
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.2.3
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-23 00:00:00.000000000 Z
11
+ date: 2016-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport