batch-loader-active-record 0.3.1 → 0.4.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: 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