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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 538bfa9bf872e0e23675e06d820897253504c6c3
4
- data.tar.gz: ece1e692df2cfd1a30d9240fc4fdfca11388f8a3
3
+ metadata.gz: 1d9a56d9aea21fdf0049781ce8d07cf85d1e996a
4
+ data.tar.gz: 9817a28de29ec3e02268234a41562925cf04adc5
5
5
  SHA512:
6
- metadata.gz: 066ce8866a53c2725e9c8e642be67e0ac2dd86fbaeab3b435c354bcf6e30255b90b1082515721ed7c132246aa30a89e3d555e8611db38033f722f9e66cb075c5
7
- data.tar.gz: 9ed65e8664ff3c288ad8dbabe17efae62b8be398ea8f1849d97ac8e72f515bcdc02bef4c4b4af10e06ddb6c6e4817bd129873e4ad743f600e33f1f784c860d85
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
- - User is `Enumerable` through `User.all`, so we have `User.count`, `User.first`, etc.
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
- - `after_create :call_method`
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 # plural ids
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
- lookup
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 intermediary_relation
11
- association.base_association.to_relation(parent_model)
12
- end
13
-
14
- def_delegators :all, :intermedia
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
@@ -1,7 +1,6 @@
1
1
  module PassiveRecord
2
2
  module Associations
3
3
  class HasOneAssociation < Struct.new(:parent_class, :child_class_name, :child_name_sym)
4
-
5
4
  def to_relation(parent_model)
6
5
  HasOneRelation.new(self, parent_model)
7
6
  end
@@ -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 < Struct.new(:klass, :conditions)
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
- assn = instance.send(field)
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 first
33
- all.first
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
@@ -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| # matching 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
- # need to loop through each arg and set id
94
- args.first.each do |child|
95
- child.send(matching_relation.parent_model_id_field + "=", id)
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}"
@@ -1,4 +1,4 @@
1
1
  module PassiveRecord
2
2
  # passive_record version
3
- VERSION = "0.3.0"
3
+ VERSION = "0.3.1"
4
4
  end
@@ -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
- after_create {@sound = 'bark'}
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
  ###
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: passive_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Weissman