batch-loader-active-record 0.3.1 → 0.4.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: 20d915e2884c4153776b3acac5c148360e66209b
4
- data.tar.gz: 7580228d4074580c688d5561ad4a3c69e599738e
3
+ metadata.gz: 677a8d4077a4cb32b88a1622cf0ff134b8ee496f
4
+ data.tar.gz: 1379446245d1cfda4b6e24b533ea6cbf10dde8ea
5
5
  SHA512:
6
- metadata.gz: 923e068adecf32ee7df60441628672ec324898ba36d5c29f866042d8ec4e6af692b19a0fa9b8c483dfdeee04066d7cf455f22c279023dd10adc997a8fa867e40
7
- data.tar.gz: f505bc5a8cfff63725a04b756f8f022c6bc95543ae734a6c8fe49e86169fb6ec18655122954cef56b5724b4ae5fe8a3079bbebc13e10bc5732bcd6cfc2fec8d7
6
+ metadata.gz: 4d4084ac2f43f85b4a1cb67cd7ed122b23ed79af1f62abb6a61afe9c09ef094f4b5934541799417d0e81f32e2d0b330c0d5b708fc5baed3f4136242efec357b1
7
+ data.tar.gz: 5fb6c5f42f3144b2b65af518efb714f372f2b48270dead47fa0892616daceca53e4d5a7c91b23095ea25a57a3b72977659154e02df7448f39a0e08622490131e
@@ -4,6 +4,10 @@ Unreleased
4
4
 
5
5
  * none
6
6
 
7
+ v0.4.0
8
+
9
+ * support `has_and_belongs_to` associations
10
+
7
11
  v0.3.1
8
12
 
9
13
  * allow to decouple declaring the assocation with Active Record DSL and generate a lazy association accessor with `association_accessor`
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- batch-loader-active-record (0.3.1)
4
+ batch-loader-active-record (0.4.0)
5
5
  activerecord (>= 4.2.0, < 5.2.0)
6
6
  activesupport (>= 4.2.0, < 5.2.0)
7
7
  batch-loader (~> 1.2.0)
data/README.md CHANGED
@@ -21,6 +21,7 @@ It is also possible to use one of the macros below in replacement of the origina
21
21
  * `belongs_to_lazy`
22
22
  * `has_one_lazy`
23
23
  * `has_many_lazy`
24
+ * `has_and_belongs_to_many_lazy`
24
25
 
25
26
  As soon as your lazy association accessor needs to do more than fetch all records of an association (using a scope or not), you're going to want to directly use the batch-loader gem. For more details on N+1 queries read the [batch-loader gem README](https://github.com/exAspArk/batch-loader/#why).
26
27
 
@@ -133,7 +134,7 @@ end
133
134
 
134
135
  class PhoneNumber < ActiveRecord::Base
135
136
  belongs_to :contact
136
- scope :active, -> { where(enabled: true) }
137
+ scope :enabled, -> { where(enabled: true) }
137
138
  end
138
139
  ```
139
140
 
@@ -143,10 +144,10 @@ This time we want the list of phone numbers for a collection of contacts.
143
144
  contacts.map(&:phone_numbers_lazy).flatten
144
145
  ```
145
146
 
146
- It is also possible to apply scopes and conditions to a lazy has_many association. For instance if we want to only fetch active phone numbers in the example above, you would specify the scope like so:
147
+ It is also possible to apply scopes and conditions to a lazy has_many association. For instance if we want to only fetch enabled phone numbers in the example above, you would specify the scope like so:
147
148
 
148
149
  ```ruby
149
- contacts.map { |contact| contact.phone_numbers_lazy(PhoneNumber.active) }.flatten
150
+ contacts.map { |contact| contact.phone_numbers_lazy(PhoneNumber.enabled) }.flatten
150
151
  ```
151
152
 
152
153
 
@@ -194,6 +195,28 @@ INNER JOIN agents ON agents.ID = phones.agent_id
194
195
  WHERE (agents. ID IN(4212, 265, 2309))
195
196
  ```
196
197
 
198
+ ### Has And Belongs To Many ###
199
+
200
+ Consider the following data model:
201
+
202
+ ```ruby
203
+ class User < ActiveRecord::Base
204
+ include BatchLoaderActiveRecord
205
+ has_and_belongs_to_many :roles
206
+ association_accessor :roles
207
+ end
208
+
209
+ class Role < ActiveRecord::Base
210
+ has_and_belongs_to_many :users
211
+ end
212
+ ```
213
+
214
+ This time we want the list of roles for a collection of users.
215
+
216
+ ```ruby
217
+ users.map(&:roles_lazy).flatten
218
+ ```
219
+
197
220
 
198
221
  ## Development
199
222
 
@@ -10,13 +10,6 @@ module BatchLoaderActiveRecord
10
10
  end
11
11
 
12
12
  module ClassMethods
13
- def belongs_to_lazy(*args)
14
- belongs_to(*args).tap do |reflections|
15
- manager = AssociationManager.new(model: self, reflection: reflections.values.last)
16
- define_method(manager.accessor_name) { manager.belongs_to_batch_loader(self) }
17
- end
18
- end
19
-
20
13
  def association_accessor(name)
21
14
  reflection = reflect_on_association(name) or raise "Can't find association #{name.inspect}"
22
15
  manager = AssociationManager.new(model: self, reflection: reflection)
@@ -29,11 +22,22 @@ module BatchLoaderActiveRecord
29
22
  define_method(manager.accessor_name) do |instance_scope = nil|
30
23
  manager.has_many_to_batch_loader(self, instance_scope)
31
24
  end
25
+ when :has_and_belongs_to_many
26
+ define_method(manager.accessor_name) do |instance_scope = nil|
27
+ manager.has_and_belongs_to_many_to_batch_loader(self, instance_scope)
28
+ end
32
29
  else
33
30
  raise NotImplementedError, "association kind #{reflection.macro.inspect} is not yet supported"
34
31
  end
35
32
  end
36
33
 
34
+ def belongs_to_lazy(*args)
35
+ belongs_to(*args).tap do |reflections|
36
+ manager = AssociationManager.new(model: self, reflection: reflections.values.last)
37
+ define_method(manager.accessor_name) { manager.belongs_to_batch_loader(self) }
38
+ end
39
+ end
40
+
37
41
  def has_one_lazy(*args)
38
42
  has_one(*args).tap do |reflections|
39
43
  manager = AssociationManager.new(model: self, reflection: reflections.values.last)
@@ -49,5 +53,14 @@ module BatchLoaderActiveRecord
49
53
  end
50
54
  end
51
55
  end
56
+
57
+ def has_and_belongs_to_many_lazy(*args)
58
+ has_and_belongs_to_many(*args).tap do |reflections|
59
+ manager = AssociationManager.new(model: self, reflection: reflect_on_all_associations.last)
60
+ define_method(manager.accessor_name) do |instance_scope = nil|
61
+ manager.has_and_belongs_to_many_to_batch_loader(self, instance_scope)
62
+ end
63
+ end
64
+ end
52
65
  end
53
66
  end
@@ -31,11 +31,7 @@ module BatchLoaderActiveRecord
31
31
  custom_key = batch_key
32
32
  custom_key += [instance_scope.to_sql.hash] unless instance_scope.nil?
33
33
  BatchLoader.for(instance.id).batch(default_value: [], key: custom_key) do |model_ids, loader|
34
- relation = if instance_scope.nil?
35
- target_scope
36
- else
37
- target_scope.instance_eval { instance_scope }
38
- end
34
+ relation = relation_with_scope(instance_scope)
39
35
  if reflection.through_reflection?
40
36
  instances = fetch_for_model_ids(model_ids, relation: relation)
41
37
  instances.each do |instance|
@@ -49,8 +45,29 @@ module BatchLoaderActiveRecord
49
45
  end
50
46
  end
51
47
 
48
+ def has_and_belongs_to_many_to_batch_loader(instance, instance_scope)
49
+ BatchLoader.for(instance.id).batch(default_value: [], key: batch_key) do |model_ids, loader|
50
+ instance_id_path = "#{reflection.join_table}.#{reflection.foreign_key}"
51
+ relation_with_scope(instance_scope)
52
+ .joins(habtm_join(reflection))
53
+ .where("#{reflection.join_table}.#{reflection.foreign_key} IN (?)", model_ids)
54
+ .select("#{target_scope.table_name}.*, #{instance_id_path} AS _instance_id")
55
+ .each do |instance|
56
+ loader.call(instance.public_send(:_instance_id)) { |value| value << instance }
57
+ end
58
+ end
59
+ end
60
+
52
61
  private
53
62
 
63
+ def relation_with_scope(instance_scope)
64
+ if instance_scope.nil?
65
+ target_scope
66
+ else
67
+ target_scope.instance_eval { instance_scope }
68
+ end
69
+ end
70
+
54
71
  def target_scope
55
72
  @target_scope ||= if reflection.scope.nil?
56
73
  reflection.klass
@@ -115,5 +132,13 @@ module BatchLoaderActiveRecord
115
132
  end
116
133
  "#{model_class.table_name}.#{id_column}"
117
134
  end
135
+
136
+ def habtm_join(reflection)
137
+ <<~SQL
138
+ INNER JOIN #{reflection.join_table}
139
+ ON #{reflection.join_table}.#{reflection.association_foreign_key} =
140
+ #{reflection.klass.table_name}.#{reflection.active_record.primary_key}
141
+ SQL
142
+ end
118
143
  end
119
144
  end
@@ -1,3 +1,3 @@
1
1
  module BatchLoaderActiveRecord
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: batch-loader-active-record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - mathieul
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-26 00:00:00.000000000 Z
11
+ date: 2017-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: batch-loader