capsula 0.0.3 → 0.0.4

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
  SHA256:
3
- metadata.gz: c534b9fef1cf66293f27706a7399215013ca7d41f58b3fd7bbf6e1f4990bc0a8
4
- data.tar.gz: 16af229a6c5155c34e8fc4e91eac9911fae50c6cfd29e5b102aa14ebae28a07d
3
+ metadata.gz: c079ef240a2cfdd558fdf3edffa7987476f278ade0831c55947011fac809ba9b
4
+ data.tar.gz: 8d99d06ef13427f10e8bab16a2ba813a7621c65e1348deae30dd3e2d8e253591
5
5
  SHA512:
6
- metadata.gz: 3480eb2df25870f7e9fd507847897cdc8cee3037bbef3c25312ef822218bf9ac54e34ecb4e7acfbf5609bc18334300fb8b71f799b5ca107989b14e17368aef12
7
- data.tar.gz: 2240b2987901d94a39d5407f0b4caa9ad943987d0e0bfe1ae4433cb1dbed18673977a13f92eaa65a4ae14c4dd52c53a70e68b43c0501e3a98ad3f636986d5643
6
+ metadata.gz: 2e9519cd42c384e75c56ec4e342d3506da9ee0ffa83a231a36eb82d87b324bcf637bc0112c594778fe1a1c22723acefe4eb2122d824a69ca639e25a3300bd9e9
7
+ data.tar.gz: e5edde7d165a6eb1f075222d6be927c98c4c39b86af12765eb8abebc4ea03eb8fdf9db8fb5308b18d22aad49a09ed143e96f34ff37346db4027cc3aa8aeef466
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- capsula (0.0.2)
4
+ capsula (0.0.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,42 +1,97 @@
1
1
  # Capsula
2
- The tool for encapsulating (preloading, including) related objects into **any** object.
2
+ The tool for preloading related objects into **any** object to prevent N+1 queries.
3
3
 
4
- ## How to use:
5
-
6
- Add to Gemfile:
4
+ ## INSTALL
7
5
 
8
6
  ```
9
7
  gem 'capsula'
10
8
  ```
11
9
 
12
- Then:
10
+ ## USE
13
11
 
14
12
  ```ruby
15
- starships = <get_your_space_fleet>
13
+ require 'capsula'
14
+
15
+ # our objects
16
+ $cars = [ {name: 'A', brand_id: 3}, {name: 'B', brand_id: 1}, {name: 'C', brand_id: 1} ]
17
+ $brands = [ {id: 1, name: 'Ferrari'}, {id: 2, name: 'Lamborghini'}, {id: 3, name: 'Rolls-Royce'} ]
18
+
19
+ # let's preload cars with brands
20
+
21
+ # our capsula-preloader
22
+ class CarPreloader < Capsula::Base
23
+ plan_for :brand,
24
+ src_key: -> (car) { car[:brand_id] },
25
+ dst_key: -> (brand) { brand[:id] },
26
+ dst_loader: -> (ids, opt) { $brands.select { |b| ids.include?(b[:id]) } }
27
+ end
28
+
29
+ # ok, preload!
30
+ $cars = CarPreloader.new($cars).plans(:brand).encapsulate
16
31
 
17
- starships = StarshipsEncapsulator.new(starships).plans(
18
- :fuel, :oxygen, :food, { spaceman: [:space_suit] }
32
+ $cars
33
+ # => [{:name=>"A", :brand_id=>3}, {:name=>"B", :brand_id=>1}, {:name=>"C", :brand_id=>1}]
34
+
35
+ # hm... where is brands?
36
+
37
+ $cars.first.brand
38
+ # => {:id=>3, :name=>"Rolls-Royce"}
39
+
40
+ $cars[0].brand
41
+ # => {:id=>3, :name=>"Rolls-Royce"}
42
+ $cars[1].brand
43
+ # => {:id=>1, :name=>"Ferrari"}
44
+ $cars[2].brand
45
+ # => {:id=>1, :name=>"Ferrari"}
46
+
47
+ # so, source objects leave untouched, but was wrapped for methods interception
48
+ ```
49
+
50
+ ### ActiveRecord case:
51
+
52
+ ```ruby
53
+ cars = Cars.where(id: [1,2,3]).to_a
54
+
55
+ cars = CarPreloader.new(cars).plans(
56
+ :fuel, :food, { driver: [:car_keys, :sunglasses] }
19
57
  ).encapsulate
20
58
 
21
- # Now let's get a suit for our spaceman:
22
- starships.first&.spaceman&.space_suit
23
- => <awesome space suit>
24
- # no actual action was triggered,
59
+ cars.first.driver.car_keys
60
+ => <car_keys>
61
+ # no actual action (any SQL query) was triggered,
25
62
  # immediate result was received from Capsula's wrapper
26
63
  ```
27
64
 
28
- Let's see how `StarshipsEncapsulator` looks:
65
+ Let's see how `CarPreloader` looks:
29
66
 
30
67
  ```ruby
31
68
  class StarshipsEncapsulator < Capsula::Base
32
- plan_for :fuel,
33
- src_key: :fuel_id,
34
- dst_key: :id,
35
- dst_loader: ->(ids,opt){ MyFuelStation.find_fuel_by_ids(ids) }
36
- # Example loader for ActiveRecord model Fuel:
37
- # dst_loader: ->(ids,opt){ Fuel.where(id: ids).includes(opt[:plans]).to_a }
38
-
39
- # Plans for other relations...
69
+ plan_for :driver,
70
+ src_key: :driver_id, # it's default value, so can be skipped
71
+ dst_key: :id, # it's default value, so can be skipped
72
+ # Example loader for ActiveRecord model Driver:
73
+ dst_loader: -> (ids, opt) {
74
+ Driver.where(id: ids).includes(opt[:plans]).to_a
75
+ }
76
+
77
+ # # Plans for other relations:
78
+ # plan_for :fuel, ...
79
+ # plan_for :food, ...
80
+ end
81
+ ```
82
+
83
+ ### has_many example
84
+
85
+ You can use custom encapsulator, but stantard encapsulator understand you if dst_key be placed inside array:
86
+
87
+ ```ruby
88
+ class Sea < Capsula::Base
89
+ plan_for :crabs,
90
+ src_key: :name,
91
+ dst_key: [:sea_name], # key inside array signals about has_many relation
92
+ dst_loader: -> (sea_names, opt) {
93
+ Crab.where(sea_name: sea_names).includes(opt[:plans]).to_a
94
+ }
40
95
  end
41
96
  ```
42
97
 
@@ -44,7 +99,7 @@ end
44
99
 
45
100
  **default values:**
46
101
 
47
- Definition for `src_key` and `dst_key` can be skipped if they values are `:fuel_id` (`<key_name>_id`) and `:id`
102
+ Definition for `src_key` and `dst_key` can be skipped if they values are `:driver_id` (`<key_name>_id`) and `:id`
48
103
 
49
104
  **lambdas:**
50
105
 
@@ -94,12 +149,13 @@ class CustomLoader
94
149
  # This method is triggered by Capsula
95
150
  def collect_ids_and_load_relations
96
151
  ids = @items.map{ |i| i.fuel_id }
97
- @store = Fuel.where(id: ids).to_a
152
+ preloads = Fuel.where(id: ids).to_a
153
+ @store = preloads.index_by(&:id)
98
154
  end
99
155
 
100
156
  # This method is calling by Capsula during encapsulation
101
157
  def get_preloads_for_object starship
102
- @store.find { |fuel| starship.fuel_id == fuel.id }
158
+ @store[starship.fuel_id]
103
159
  end
104
160
  end
105
161
 
data/capsula.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'capsula'
3
- s.version = '0.0.3'
4
- s.date = '2019-02-04'
3
+ s.version = '0.0.4'
4
+ s.date = '2019-05-29'
5
5
  s.summary = 'Encapsulating tool'
6
6
  s.description = 'The tool for encapsulating (preloading, including) related objects'
7
7
  s.authors = ['Rodion V']
@@ -108,8 +108,14 @@ module Capsula
108
108
  col =
109
109
  if ids.any?
110
110
  preloads = dec[:dst_loader].call(ids,opt)
111
- # build hash: {id1 => Obj1, ..., idN => ObjN }
112
- index_array_by(preloads) { |el| get_value(el, dec[:dst_key]) }
111
+ dst_key = dec[:dst_key]
112
+ if dst_key.is_a?(Array)
113
+ # build hash: {id1 => [Obj1, ...], idN => [ObjN] }
114
+ preloads.group_by { |o| get_value(o, dst_key.first) }
115
+ else
116
+ # build hash: {id1 => Obj1, ..., idN => ObjN }
117
+ index_array_by(preloads) { |el| get_value(el, dst_key) }
118
+ end
113
119
  else
114
120
  {}
115
121
  end
@@ -130,12 +136,16 @@ module Capsula
130
136
  def native_encapsulation plan_key, preloaded_collection, declaration
131
137
  col = preloaded_collection
132
138
  dec = declaration
139
+ is_dst_key_array = dec[:dst_key].is_a?(Array)
133
140
 
134
141
  @items.each do |i|
135
142
  src_id = get_value(i, dec[:src_key])
136
143
 
137
144
  val =
138
- if src_id.is_a?(Array)
145
+ if is_dst_key_array
146
+ # src has many dst
147
+ col[src_id] || []
148
+ elsif src_id.is_a?(Array)
139
149
  # if object has many links to related objects (Array)
140
150
  col.values_at(*src_id)
141
151
  else
@@ -151,6 +161,8 @@ module Capsula
151
161
  case key_or_lambda
152
162
  when ::Symbol, ::String
153
163
  _object_.send(key_or_lambda)
164
+ when ::Array
165
+ _object_.send(key_or_lambda[0])
154
166
  when ::Proc
155
167
  key_or_lambda.call(_object_)
156
168
  else
data/spec/capsula_spec.rb CHANGED
@@ -2,10 +2,11 @@ require 'capsula'
2
2
 
3
3
  RSpec.describe Capsula do
4
4
  let(:klass){ Class.new(Capsula::Base) }
5
- let(:a) { Struct.new(:b_id) }
6
- let(:b) { Struct.new(:id) }
7
5
 
8
6
  describe 'User can' do
7
+ let(:a) { Struct.new(:b_id) }
8
+ let(:b) { Struct.new(:id) }
9
+
9
10
  it 'declare new encapsulation plan' do
10
11
  klass.plan_for(:b, src_key: :b_id, dst_key: :id, dst_loader: ->(i,o){})
11
12
  plans = klass.instance_variable_get('@plans_declarations')
@@ -82,7 +83,32 @@ RSpec.describe Capsula do
82
83
  expect(objects.first).to be_a(Capsula::Wrapper)
83
84
  expect(objects.first.b.id).to be == objects.first.b_id
84
85
  end
86
+ end
87
+
88
+ describe 'has_many plan' do
89
+ let(:a) { Struct.new(:id) }
90
+ let(:b) { Struct.new(:a_id) }
91
+
92
+ it 'working' do
93
+ b_store = [1,2,2,3].map { |a_id| b.new(a_id) }
94
+ klass.plan_for(
95
+ :bs,
96
+ src_key: :id,
97
+ dst_key: [:a_id],
98
+ dst_loader: ->(ids,opt){ b_store.select { |b| ids.include?(b.a_id) } }
99
+ )
85
100
 
101
+ as = [2,3,4].map { |id| a.new(id) }
102
+ as = klass.new(as).plans(:bs).encapsulate
103
+
104
+ expect(as[0]).to respond_to(:bs)
105
+ expect(as[0]).to be_a(Capsula::Wrapper)
106
+ expect(as[0].bs.count).to eq(2)
107
+ expect(as[1].bs.count).to eq(1)
108
+ expect(as[2].bs.count).to eq(0)
109
+ expect(as[0].bs[0].a_id).to eq(as[0].id)
110
+ expect(as[1].bs[0].a_id).to eq(as[1].id)
111
+ end
86
112
  end
87
113
 
88
114
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capsula
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodion V
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-04 00:00:00.000000000 Z
11
+ date: 2019-05-29 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: The tool for encapsulating (preloading, including) related objects
14
14
  email: rodion.v@devaer.com