autobots 0.0.1 → 0.1.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: 2f3045074e374f37719b3922bfea5023f73fa5c0
4
- data.tar.gz: 19c06ac4b4e923aee25392adcc4bfb5577f4b183
3
+ metadata.gz: cced0fc73b998d0e2c7ec8d62db5006e4e3191fd
4
+ data.tar.gz: df8529087829518b6982070e58c443c779588fc9
5
5
  SHA512:
6
- metadata.gz: 290662e0a21676241bca53b76cc4c966f9733d095291a5c167c78ff4f2b8e7b483a8d1908ad93d352571fba8ac6fbe2569733e1938330be00bd3f26b477ffb8d
7
- data.tar.gz: 13c683363bd0f4230e447440e38168c9dd0497e0e15a68d725c6e4e907c22bdc16352159b09113a38c0b0626306e9c134723b9a5e5e6555dfa399a05b4a5ae5f
6
+ metadata.gz: 253704d48566fe2a4be8c11d2c3274b84045e739592baef54d80982539b73096fa270f1706918efa77f4c755ff95a6e855ba9ef884a5950c240d0a511041c92a
7
+ data.tar.gz: 91cf503c5dc1136547588320aba92696320476b252a48796e3ff85bb7f603f67695839650a60576ab442eff784695e772eb688c069043dfcd3c1f74f9c5b1757
data/README.md CHANGED
@@ -4,134 +4,135 @@ Loading and serializing models in bulk with caching.
4
4
 
5
5
  Separate the loading/serialization of resources from their protocol (http/json, avro, xml).
6
6
 
7
- ## Motivation
8
-
9
7
  We want to improve api response time by loading the minimum amount of data needed to check cache keys. If we miss, we want to optimally load our data, serialize it, cache it and then return it.
10
8
 
11
- ## Installation
12
-
13
- Add this line to your application's Gemfile:
9
+ ## Problem
14
10
 
15
- ```ruby
16
- gem 'autobots'
17
- ```
11
+ Say we have a simple api action:
18
12
 
19
- And then execute:
13
+ def index
14
+ # find our records
15
+ projects = Project.includes(issues: :comments).first(10)
16
+ render json: projects, each_serializer: ProjectSerializer
17
+ end
20
18
 
21
- $ bundle
19
+ How do we cache this?
22
20
 
23
- Or install it yourself as:
21
+ class ProjectSerializer < ActiveModel::Serializer
22
+ cached
23
+ end
24
24
 
25
- $ gem install autobots
25
+ def index
26
+ projects = Project.includes(issues: :comments).first(10)
27
+ render json: projects, each_serializer: ProjectSerializer
28
+ end
26
29
 
27
- ## Usage
28
-
29
- When trying to render a resource for an API, we have 3 parts:
30
-
31
- 1. Fetching data
32
- 2. Figuring out the data to return
33
- 3. Format it for the protocol
30
+ There are 2 problems with this approach:
34
31
 
35
- ### Fetching data
32
+ 1. We're making 3 sql calls every time regardless of what keys are required for checking the cache
33
+ 2. We're making 10 cache fetch requests
36
34
 
37
- `autobots` uses `Autobots::Assembler` classes to fetch data from given identifiers. The assembler's job is to fetch all data needed for serialization as optimally as possible. The identifiers should be the minimum amount of data needed to fetch data and determine caching.
35
+ We can fix this by fetching the bare minimum amount of data needed to check the cache, checking the cache in bulk, then loading the data needed in an optimized fashion for cache misses only.
38
36
 
39
- ```ruby
40
-
41
- class ProjectAssembler < Autobots::Assembler
42
-
43
- # assembles the base objects needed for cache key generation
44
- def assemble(identifiers)
45
- Project.where(id: identifiers).to_a
46
- end
47
-
48
- end
37
+ This gem's goal is to abstract all that logic into a testable, declarative model that can improve your api's response time. We also want to return serializable data so that we decouple the definition of a resource from its protocols (not locked to json or xml)
49
38
 
39
+ ## Usage
50
40
 
51
- project_ids = [1,2,3]
52
- assembler = ProjectAssembler.new(project_ids)
41
+ An `Autobots::Assembler` is the core of the `autobots` gem. The input for an autobot is that you provide an array of objects needed to build your cache keys. The output is an array of serializable data that corresponds to the input set (retaining order).
53
42
 
54
- # returns preloaded objects for serialization
55
- resources = assembler.resources
43
+ ![flow](docs/flow.png)
56
44
 
57
- ```
45
+ ### Lifecycle of an Autobot
58
46
 
59
- ### Figuring out the data to return
47
+ When trying to render a resource for an API, we have 3 parts:
60
48
 
61
- We use the `active_model_serializers` gem to accomplish serialization. An assembler declares the type of serializer used to specify the data returned
49
+ 1. Fetching data
50
+ 2. Figuring out the data to return
51
+ 3. Format it for the protocol
62
52
 
63
- ```ruby
53
+ Each of these methods corresponds to a single lifecycle method of an autobot:
64
54
 
65
- class ProjectAssembler < Autobots::Assembler
66
- self.serializer = ProjectSerializer # an ActiveModel::Serializer
67
- end
55
+ 1. `assemble`
56
+ 2. `transform`
57
+ 3. `roll_out`
68
58
 
69
- ```
70
59
 
71
- ### Formatting the response
60
+ #### Fetching data (assemble)
72
61
 
73
- `autobots` gem only handles the loading and representation of the data.
62
+ An `assembler` can optionally load whatever data is required to build it's cache keys. The assembler's job is to fetch all data needed for serialization as optimally as possible. The identifiers should be the minimum amount of data needed to fetch data and determine caching.
74
63
 
75
- ### Caching
64
+ class ProjectAssembler < Autobots::Assembler
65
+
66
+ # assembles the base objects needed for cache key generation
67
+ def assemble(identifiers)
68
+ Project.where(id: identifiers).to_a
69
+ end
70
+
71
+ end
76
72
 
77
- We can get large performance boosts by caching our serializable data. Caching is straight forward:
73
+ project_ids = [1,2,3]
74
+ assembler = ProjectAssembler.new(project_ids)
75
+
76
+ # returns preloaded objects for serialization
77
+ objects = assembler.objects
78
78
 
79
- ```ruby
79
+ #### Figuring out the data to return (transform)
80
80
 
81
- # some sort of ActiveSupport::CacheStore
82
- cache = Rails.cache
83
- assembler = ProjectAssembler.new(project_ids, cache: cache)
81
+ The transform lifecycle event is called with every object that needs refreshing. It allows us to to optimize our loading of nested resources by using bulk loading. If caching is enabled, we only need to run this on resources that missed the cache. We may not even need to run it at all!
84
82
 
85
- ```
83
+ An example of this would be using `ActiveRecord::Associations::Preloader` to fetch included records if necessary:
86
84
 
87
- The cache store must implement `read_multi` as we use [`bulk_cache_fetcher` gem](https://github.com/justinweiss/bulk_cache_fetcher/)
85
+ class ProjectAssembler < Autobots::Assembler
86
+ def transform(resources)
87
+ ActiveRecord::Associations::Preloader.new().preload(resources, {issues: :comments})
88
+ end
89
+ end
88
90
 
89
- By default, we use each resource's `cache_key` implementation. You can provide your own cache key generator by passing in a cache_key proc:
91
+ Since this is a fairly common pattern, I created the `Autobots::ActiveRecordAssembler` that has this default behavior:
90
92
 
91
- ```ruby
93
+ class ProjectAssembler < Autobots::ActiveRecordAssembler
94
+ self.preloads = {issues: :comments}
95
+ end
92
96
 
93
- assembler = ProjectAssembler.new(project_ids, {
94
- cache: cache,
95
- cache_key: -> (obj, assembler) {
96
- "#{obj.cache_key}-foo"
97
- }
98
- })
97
+ The strength of this model lies in minimizing data fetch requests when building our data to serialize. We do this by delaying the loading of nested models and only load them if we have to.
99
98
 
100
- ```
99
+ If a resources's cache is up to date, we shouldn't have to fetch it's dependencies from the database. However, if we have a cache miss, we want to optimally load the data needed.
101
100
 
102
- ### Minimizing fetch requests
101
+ #### Formatting the response (roll_out)
103
102
 
104
- The strength of this model lies in minimizing data fetch requests when building our data to serialize. We do this by delaying the loading of nested models and only load them if we have to.
103
+ We use the `active_model_serializers` gem to accomplish serialization. An assembler declares the type of serializer used to specify the data returned
105
104
 
106
- If a resources's cache is up to date, we shouldn't have to fetch it's dependencies from the database. However, if we have a cache miss, we want to optimally load the data needed.
107
105
 
108
- ```ruby
106
+ class ProjectAssembler < Autobots::Assembler
107
+ self.serializer = ProjectSerializer # an ActiveModel::Serializer
108
+ end
109
+
110
+ # returns an array of data for serialization
111
+ data = assembler.data
109
112
 
110
- class ProjectAssembler < Autobots::Assembler
113
+ The default behavior of an assembler is to wrap each object with the declared serializer and returning its serializable_hash.
111
114
 
112
- # for any missing resources, preload the nested attributes
113
- def transform(resources)
114
- ActiveRecord::Associations::Preloader.new.preload(resources, {issues: :comments})
115
- end
115
+ `autobots` gem only handles the loading and representation of the data.
116
116
 
117
- end
117
+ ### Caching
118
118
 
119
- ```
119
+ We can get large performance boosts by caching our serializable data. Caching is straight forward:
120
120
 
121
- The preloading step can be customized (load from an external service or whatever you want). We provide a helpful mixin if you're using ActiveRecord and you want preloading:
121
+ # some sort of ActiveSupport::CacheStore
122
+ cache = Rails.cache
123
+ assembler = ProjectAssembler.new(project_ids, cache: cache)
122
124
 
123
- ```ruby
125
+ The cache store must implement `read_multi` as we use [`bulk_cache_fetcher` gem](https://github.com/justinweiss/bulk_cache_fetcher/)
124
126
 
125
- class ProjectAssembler < Autobots::Assembler
126
- include Autobots::Helpers::ActiveRecordPreloading
127
+ By default, we use each resource's `cache_key` implementation. You can provide your own cache key generator by passing in a cache_key proc:
127
128
 
128
- # in this case, project has_many issues which has_many comments
129
- def preloads
130
- {issues: :comments}
131
- end
132
- end
129
+ assembler = ProjectAssembler.new(project_ids, {
130
+ cache: cache,
131
+ cache_key: -> (obj, assembler) {
132
+ "#{obj.cache_key}-foo"
133
+ }
134
+ })
133
135
 
134
- ```
135
136
 
136
137
  ## Contributing
137
138
 
Binary file
@@ -4,6 +4,7 @@ require 'active_model_serializers'
4
4
  require 'bulk_cache_fetcher'
5
5
 
6
6
  module Autobots
7
+ autoload :ActiveRecordAssembler, 'autobots/active_record_assembler'
7
8
  autoload :Assembler, 'autobots/assembler'
8
9
  autoload :Helpers, 'autobots/helpers'
9
10
  end
@@ -0,0 +1,5 @@
1
+ module Autobots
2
+ class ActiveRecordAssembler < Assembler
3
+ include Helpers::ActiveRecordPreloading
4
+ end
5
+ end
@@ -1,6 +1,12 @@
1
1
  module Autobots
2
2
  module Helpers
3
3
  module ActiveRecordPreloading
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :preloads
8
+ self.preloads = []
9
+ end
4
10
 
5
11
  def transform(objects)
6
12
  ActiveRecord::Associations::Preloader.new(objects, preloads).run
@@ -10,9 +16,6 @@ module Autobots
10
16
  objects
11
17
  end
12
18
 
13
- def preloads
14
- []
15
- end
16
19
  end
17
20
  end
18
21
  end
@@ -1,3 +1,3 @@
1
1
  module Autobots
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -31,10 +31,8 @@ class ProjectPreloadAssembler < Autobots::Assembler
31
31
  end
32
32
  end
33
33
 
34
- class ProjectPreloadIncludedAssembler < Autobots::Assembler
34
+ class ProjectPreloadIncludedAssembler < Autobots::ActiveRecordAssembler
35
35
  self.serializer = ProjectSerializer
36
- include Autobots::Helpers::ActiveRecordPreloading
37
-
38
36
  def preloads
39
37
  {issues: :comments}
40
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: autobots
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Ching
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-21 00:00:00.000000000 Z
11
+ date: 2014-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bulk_cache_fetcher
@@ -108,7 +108,9 @@ files:
108
108
  - README.md
109
109
  - Rakefile
110
110
  - autobots.gemspec
111
+ - docs/flow.png
111
112
  - lib/autobots.rb
113
+ - lib/autobots/active_record_assembler.rb
112
114
  - lib/autobots/assembler.rb
113
115
  - lib/autobots/helpers.rb
114
116
  - lib/autobots/helpers/active_record_preloading.rb