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 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