kashmir 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +49 -0
- data/LICENSE.txt +22 -0
- data/README.md +563 -0
- data/Rakefile +11 -0
- data/kashmir.gemspec +35 -0
- data/lib/kashmir.rb +42 -0
- data/lib/kashmir/caching.rb +50 -0
- data/lib/kashmir/dsl.rb +41 -0
- data/lib/kashmir/extensions.rb +18 -0
- data/lib/kashmir/inline_dsl.rb +13 -0
- data/lib/kashmir/patches/active_record.rb +68 -0
- data/lib/kashmir/plugins/active_record_representation.rb +14 -0
- data/lib/kashmir/plugins/ar.rb +49 -0
- data/lib/kashmir/plugins/ar_relation.rb +35 -0
- data/lib/kashmir/plugins/memcached_caching.rb +83 -0
- data/lib/kashmir/plugins/memory_caching.rb +66 -0
- data/lib/kashmir/plugins/null_caching.rb +42 -0
- data/lib/kashmir/representable.rb +88 -0
- data/lib/kashmir/representation.rb +117 -0
- data/lib/kashmir/version.rb +3 -0
- data/test/activerecord_test.rb +127 -0
- data/test/activerecord_tricks_test.rb +54 -0
- data/test/ar_test_helper.rb +34 -0
- data/test/caching_test.rb +197 -0
- data/test/dsl_test.rb +162 -0
- data/test/inline_dsl_test.rb +108 -0
- data/test/kashmir_test.rb +216 -0
- data/test/support/ar_models.rb +69 -0
- data/test/support/factories.rb +33 -0
- data/test/support/schema.rb +32 -0
- data/test/test_helper.rb +9 -0
- metadata +217 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c5780c967e6ff6ba5b26f80137d30266d1d55554
|
4
|
+
data.tar.gz: e4b5a243cfaa28a516c9637457a34f880612d766
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 88b891a003b3a760036888515d56b6dfbc2bab338103c3f2a146d10dd61b5ed887ab438e40e10b8f786bb3db9e8388114852d952821e3685aea3ef6df6507c9f
|
7
|
+
data.tar.gz: d1ee5652b5359b418efebe856de46738322eb6213575c9a49e0b71753064313cd2c3e62a7e7dab3bfc64920266ad614857e9e92b35bd02075502e1d4e1ad5709
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
kashmir (0.0.1)
|
5
|
+
colorize (~> 0.7)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (3.2.22)
|
11
|
+
activesupport (= 3.2.22)
|
12
|
+
builder (~> 3.0.0)
|
13
|
+
activerecord (3.2.22)
|
14
|
+
activemodel (= 3.2.22)
|
15
|
+
activesupport (= 3.2.22)
|
16
|
+
arel (~> 3.0.2)
|
17
|
+
tzinfo (~> 0.3.29)
|
18
|
+
activesupport (3.2.22)
|
19
|
+
i18n (~> 0.6, >= 0.6.4)
|
20
|
+
multi_json (~> 1.0)
|
21
|
+
arel (3.0.3)
|
22
|
+
builder (3.0.4)
|
23
|
+
colorize (0.7.7)
|
24
|
+
dalli (2.7.4)
|
25
|
+
i18n (0.7.0)
|
26
|
+
metaclass (0.0.4)
|
27
|
+
minitest (5.5.1)
|
28
|
+
minitest-around (0.3.2)
|
29
|
+
minitest (~> 5.0)
|
30
|
+
mocha (1.1.0)
|
31
|
+
metaclass (~> 0.0.1)
|
32
|
+
multi_json (1.11.1)
|
33
|
+
rake (10.4.2)
|
34
|
+
sqlite3 (1.3.10)
|
35
|
+
tzinfo (0.3.44)
|
36
|
+
|
37
|
+
PLATFORMS
|
38
|
+
ruby
|
39
|
+
|
40
|
+
DEPENDENCIES
|
41
|
+
activerecord (~> 3.2.8)
|
42
|
+
bundler (~> 1.7)
|
43
|
+
dalli (~> 2.7)
|
44
|
+
kashmir!
|
45
|
+
minitest (~> 5.0)
|
46
|
+
minitest-around (~> 0.3)
|
47
|
+
mocha (~> 1.1.0)
|
48
|
+
rake (~> 10.0)
|
49
|
+
sqlite3 (= 1.3.10)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Netto Farah
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,563 @@
|
|
1
|
+
# Kashmir
|
2
|
+
|
3
|
+
Kashmir is a DSL built to allow developers to describe representations of their ruby objects.
|
4
|
+
Kashmir will turn these ruby objects into `Hash`es that represent the dependency tree you just described.
|
5
|
+
|
6
|
+
`Kashmir::ActiveRecord` will also optimize and try to balance `ActiveRecord` queries, so your application hits the database as little as possible.
|
7
|
+
|
8
|
+
`Kashmir::Caching` builds a dependency tree for complex object representations and caches each level of this tree separately. Kashmir will do so by creating cache views of each one of these layers, but also caching a complete tree.
|
9
|
+
The caching engine is smart enough to fill out holes in the cache tree with fresh data from your database or another sort of data store.
|
10
|
+
|
11
|
+
Combine `Kashmir::Caching` + `Kashmir::ActiveRecord` for extra awesomeness.
|
12
|
+
|
13
|
+
### Example:
|
14
|
+
|
15
|
+
For example, a `Person` with a `name` and `age`:
|
16
|
+
```ruby
|
17
|
+
class Person
|
18
|
+
include Kashmir
|
19
|
+
|
20
|
+
def initialize(name, age)
|
21
|
+
@name = name
|
22
|
+
@age = age
|
23
|
+
end
|
24
|
+
|
25
|
+
representations do
|
26
|
+
rep :name
|
27
|
+
rep :age
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
31
|
+
could be represented as:
|
32
|
+
```
|
33
|
+
{ name: 'Netto Farah', age: 26 }
|
34
|
+
```
|
35
|
+
|
36
|
+
Representing an object is as simple as:
|
37
|
+
1. add `include Kashmir` to the target class
|
38
|
+
2. whitelist all the fields you may want in a representation.
|
39
|
+
```ruby
|
40
|
+
# add all the fields, methods you want to be visible to Kashmir
|
41
|
+
representations do
|
42
|
+
rep(:name) # this exposes the property name to Kashmir
|
43
|
+
rep(:age)
|
44
|
+
end
|
45
|
+
```
|
46
|
+
3. Instantiate an object and `#represent` it.
|
47
|
+
```ruby
|
48
|
+
# you can pass in an array with all the fields you want to be represented
|
49
|
+
Person.new('Netto Farah', 26).represent([:name, :age])
|
50
|
+
=> {:name=>"Netto Farah", :age=>"26"}
|
51
|
+
```
|
52
|
+
|
53
|
+
## Installation
|
54
|
+
|
55
|
+
Add this line to your application's Gemfile:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
gem 'kashmir'
|
59
|
+
```
|
60
|
+
|
61
|
+
And then execute:
|
62
|
+
|
63
|
+
$ bundle
|
64
|
+
|
65
|
+
## Usage
|
66
|
+
Kashmir is better described with examples.
|
67
|
+
|
68
|
+
### Basic representations
|
69
|
+
|
70
|
+
#### Describing an object
|
71
|
+
Only whitelisted fields can be represented by Kashmir.
|
72
|
+
This is done so secret fields don't get exposed to clients, such as passwords and salts.
|
73
|
+
|
74
|
+
``` ruby
|
75
|
+
class Recipe < OpenStruct
|
76
|
+
include Kashmir
|
77
|
+
|
78
|
+
representations do
|
79
|
+
rep(:title)
|
80
|
+
rep(:preparation_time)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
Instantiate a `Recipe`:
|
86
|
+
```ruby
|
87
|
+
recipe = Recipe.new(title: 'Beef stew', preparation_time: 60)
|
88
|
+
```
|
89
|
+
|
90
|
+
Kashmir automatically adds a `#represent` method to every instance of `Recipe` now.
|
91
|
+
`#represent` will take an `Array` with all the fields you want as part of your representation.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
recipe.represent([:title, :preparation_time])
|
95
|
+
=> { title: 'Beef stew', preparation_time: 60 }
|
96
|
+
```
|
97
|
+
#### Calculated fields
|
98
|
+
You can represent any instance variable or method (basically anything that returns `true` for `respond_to?`).
|
99
|
+
``` ruby
|
100
|
+
class Recipe < OpenStruct
|
101
|
+
include Kashmir
|
102
|
+
|
103
|
+
representations do
|
104
|
+
rep(:title)
|
105
|
+
rep(:num_steps)
|
106
|
+
end
|
107
|
+
|
108
|
+
def num_steps
|
109
|
+
steps.size
|
110
|
+
end
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
Recipe.new(title: 'Beef stew', steps: ['chop', 'cook']).represent([:title, :num_steps])
|
116
|
+
=> { title: 'Beef stew', num_steps: 2 }
|
117
|
+
```
|
118
|
+
|
119
|
+
### Nested representations
|
120
|
+
You can nest Kashmir objects to represent complex relationships between your objects.
|
121
|
+
```ruby
|
122
|
+
class Recipe < OpenStruct
|
123
|
+
include Kashmir
|
124
|
+
|
125
|
+
representations do
|
126
|
+
rep(:title)
|
127
|
+
rep(:chef)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Chef < OpenStruct
|
132
|
+
include Kashmir
|
133
|
+
|
134
|
+
representations do
|
135
|
+
base([:name])
|
136
|
+
end
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
Nested representations can be described as hashes inside your array of fields.
|
141
|
+
You can then pass in another array with all the properties you want to present in the nested object.
|
142
|
+
```ruby
|
143
|
+
netto = Chef.new(name: 'Netto Farah')
|
144
|
+
beef_stew = Recipe.new(title: 'Beef Stew', chef: netto)
|
145
|
+
|
146
|
+
beef_stew.represent([:title, { :chef => [ :name ] }])
|
147
|
+
=> {
|
148
|
+
:title => "Beef Stew",
|
149
|
+
:chef => {
|
150
|
+
:name => 'Netto Farah'
|
151
|
+
}
|
152
|
+
}
|
153
|
+
```
|
154
|
+
Not happy with this syntax? Check out `Kashmir::DSL` or `Kashmir::InlineDSL` for prettier code.
|
155
|
+
|
156
|
+
#### Base representations
|
157
|
+
Tired of repeating the same fields over and over?
|
158
|
+
You can create a base representation of your objects, so Kashmir returns basic fields automatically.
|
159
|
+
```ruby
|
160
|
+
class Recipe
|
161
|
+
include Kashmir
|
162
|
+
|
163
|
+
representations do
|
164
|
+
base [:title, :preparation_time]
|
165
|
+
rep :num_steps
|
166
|
+
rep :chef
|
167
|
+
end
|
168
|
+
end
|
169
|
+
```
|
170
|
+
`base(...)` takes an array with the fields you want to return on every representation of a given class.
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
brisket = Recipe.new(title: 'BBQ Brisket', preparation_time: 'a long time')
|
174
|
+
brisket.represent()
|
175
|
+
=> { :title => 'BBQ Brisket', :preparation_time => 'a long time' }
|
176
|
+
```
|
177
|
+
|
178
|
+
### Complex Representations
|
179
|
+
You can nest as many Kashmir objects as you want.
|
180
|
+
```ruby
|
181
|
+
class Recipe < OpenStruct
|
182
|
+
include Kashmir
|
183
|
+
|
184
|
+
representations do
|
185
|
+
base [:title]
|
186
|
+
rep :chef
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class Chef < OpenStruct
|
191
|
+
include Kashmir
|
192
|
+
|
193
|
+
representations do
|
194
|
+
base :name
|
195
|
+
rep :restaurant
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
class Restaurant < OpenStruct
|
200
|
+
include Kashmir
|
201
|
+
|
202
|
+
representations do
|
203
|
+
base [:name]
|
204
|
+
rep :rating
|
205
|
+
end
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
bbq_joint = Restaurant.new(name: "Netto's BBQ Joint", rating: '5 stars')
|
211
|
+
netto = Chef.new(name: 'Netto', restaurant: bbq_joint)
|
212
|
+
brisket = Recipe.new(title: 'BBQ Brisket', chef: netto)
|
213
|
+
|
214
|
+
brisket.represent([
|
215
|
+
:chef => [
|
216
|
+
{ :restaurant => [ :rating ] }
|
217
|
+
]
|
218
|
+
])
|
219
|
+
|
220
|
+
=> {
|
221
|
+
title: 'BBQ Brisket',
|
222
|
+
chef: {
|
223
|
+
name: 'Netto',
|
224
|
+
restaurant: {
|
225
|
+
name: "Netto's BBQ Joint",
|
226
|
+
rating: '5 stars'
|
227
|
+
}
|
228
|
+
}
|
229
|
+
}
|
230
|
+
|
231
|
+
```
|
232
|
+
|
233
|
+
|
234
|
+
### Collections
|
235
|
+
Arrays of Kashmir objects work the same way as any other Kashmir representations.
|
236
|
+
Kashmir will augment `Array` with `#represent` that will represent every item in the array.
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
class Ingredient < OpenStruct
|
240
|
+
include Kashmir
|
241
|
+
|
242
|
+
representations do
|
243
|
+
rep(:name)
|
244
|
+
rep(:quantity)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
class ClassyRecipe < OpenStruct
|
249
|
+
include Kashmir
|
250
|
+
|
251
|
+
representations do
|
252
|
+
rep(:title)
|
253
|
+
rep(:ingredients)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
```
|
257
|
+
```ruby
|
258
|
+
omelette = ClassyRecipe.new(title: 'Omelette Du Fromage')
|
259
|
+
omelette.ingredients = [
|
260
|
+
Ingredient.new(name: 'Egg', quantity: 2),
|
261
|
+
Ingredient.new(name: 'Cheese', quantity: 'a lot!')
|
262
|
+
]
|
263
|
+
```
|
264
|
+
Just describe your `Array` representations like any regular nested representation.
|
265
|
+
```ruby
|
266
|
+
omelette.represent([:title, {
|
267
|
+
:ingredients => [ :name, :quantity ]
|
268
|
+
}
|
269
|
+
])
|
270
|
+
```
|
271
|
+
```ruby
|
272
|
+
=> {
|
273
|
+
title: 'Omelette Du Fromage',
|
274
|
+
ingredients: [
|
275
|
+
{ name: 'Egg', quantity: 2 },
|
276
|
+
{ name: 'Cheese', quantity: 'a lot!' }
|
277
|
+
]
|
278
|
+
}
|
279
|
+
```
|
280
|
+
### `Kashmir::Dsl`
|
281
|
+
Passing arrays and hashes around can be very tedious and lead to duplication.
|
282
|
+
`Kashmir::Dsl` allows you to create your own representers/decorators so you can keep your logic in one place and make way more expressive.
|
283
|
+
|
284
|
+
```ruby
|
285
|
+
class Recipe < OpenStruct
|
286
|
+
include Kashmir
|
287
|
+
|
288
|
+
representations do
|
289
|
+
rep(:title)
|
290
|
+
rep(:num_steps)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
class RecipeRepresenter
|
295
|
+
include Kashmir::Dsl
|
296
|
+
|
297
|
+
prop :title
|
298
|
+
prop :num_steps
|
299
|
+
end
|
300
|
+
```
|
301
|
+
|
302
|
+
All you need to do is include `Kashmir::Dsl` in any ruby class. Every call to `prop(field_name)` will translate directly into just adding an extra field in the representation array.
|
303
|
+
|
304
|
+
|
305
|
+
In this case, `RecipeRepresenter` will translate directly to `[:title, :num_steps]`.
|
306
|
+
|
307
|
+
```ruby
|
308
|
+
brisket = Recipe.new(title: 'BBQ Brisket', num_steps: 2)
|
309
|
+
brisket.represent(RecipePresenter)
|
310
|
+
|
311
|
+
=> { title: 'BBQ Brisket', num_steps: 2 }
|
312
|
+
```
|
313
|
+
#### Embed Representers
|
314
|
+
It is also possible to define nested representers with `embed(:property_name, RepresenterClass)`.
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
class RecipeWithChefRepresenter
|
318
|
+
include Kashmir::Dsl
|
319
|
+
|
320
|
+
prop :title
|
321
|
+
embed :chef, ChefRepresenter
|
322
|
+
end
|
323
|
+
|
324
|
+
class ChefRepresenter
|
325
|
+
include Kashmir::Dsl
|
326
|
+
|
327
|
+
prop :full_name
|
328
|
+
end
|
329
|
+
```
|
330
|
+
Kashmir will inline these classes and return a raw Kashmir description.
|
331
|
+
```ruby
|
332
|
+
RecipeWithChefRepresenter.definitions == [ :title, { :chef => [ :full_name ] }]
|
333
|
+
=> true
|
334
|
+
```
|
335
|
+
Representing the objects will work just as before.
|
336
|
+
```ruby
|
337
|
+
chef = Chef.new(first_name: 'Netto', last_name: 'Farah')
|
338
|
+
brisket = Recipe.new(title: 'BBQ Brisket', chef: chef)
|
339
|
+
|
340
|
+
brisket.represent(RecipeWithChefRepresenter)
|
341
|
+
|
342
|
+
=> {
|
343
|
+
title: 'BBQ Brisket',
|
344
|
+
chef: {
|
345
|
+
full_name: 'Netto Farah'
|
346
|
+
}
|
347
|
+
}
|
348
|
+
```
|
349
|
+
#### Inline Representers
|
350
|
+
You don't necessarily need to define a class for every nested representation.
|
351
|
+
```ruby
|
352
|
+
class RecipeWithInlineChefRepresenter
|
353
|
+
include Kashmir::Dsl
|
354
|
+
|
355
|
+
prop :title
|
356
|
+
|
357
|
+
inline :chef do
|
358
|
+
prop :full_name
|
359
|
+
end
|
360
|
+
end
|
361
|
+
```
|
362
|
+
Using `inline(:property_name, &block)` will work the same way as `embed`. Except that you can now define short representations using ruby blocks. Leading us to our next topic.
|
363
|
+
|
364
|
+
### `Kashmir::InlineDsl`
|
365
|
+
`Kashmir::InlineDsl` sits right in between raw representations and Representers. It reads much better than arrays of hashes and provides the expressiveness of `Kashmir::Dsl` without all the ceremony.
|
366
|
+
|
367
|
+
It works with every feature from `Kashmir::Dsl` and allows you to define quick inline descriptions for your `Kashmir` objects.
|
368
|
+
|
369
|
+
```ruby
|
370
|
+
class Recipe < OpenStruct
|
371
|
+
include Kashmir
|
372
|
+
|
373
|
+
representations do
|
374
|
+
rep(:title)
|
375
|
+
rep(:num_steps)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
```
|
379
|
+
Just call `#represent_with(&block)` on any `Kashmir` object and use the `Kashmir::Dsl` syntax.
|
380
|
+
```ruby
|
381
|
+
brisket = Recipe.new(title: 'BBQ Brisket', num_steps: 2)
|
382
|
+
|
383
|
+
brisket.represent_with do
|
384
|
+
prop :title
|
385
|
+
prop :num_steps
|
386
|
+
end
|
387
|
+
|
388
|
+
=> { title: 'BBQ Brisket', num_steps: 2 }
|
389
|
+
```
|
390
|
+
|
391
|
+
#### Nested inline representations
|
392
|
+
You can nest inline representations using `inline(:field, &block)` the same way we did with `Kashmir::Dsl`.
|
393
|
+
|
394
|
+
```ruby
|
395
|
+
class Ingredient < OpenStruct
|
396
|
+
include Kashmir
|
397
|
+
|
398
|
+
representations do
|
399
|
+
rep(:name)
|
400
|
+
rep(:quantity)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
class ClassyRecipe < OpenStruct
|
405
|
+
include Kashmir
|
406
|
+
|
407
|
+
representations do
|
408
|
+
rep(:title)
|
409
|
+
rep(:ingredients)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
```
|
413
|
+
```ruby
|
414
|
+
omelette = ClassyRecipe.new(title: 'Omelette Du Fromage')
|
415
|
+
omelette.ingredients = [
|
416
|
+
Ingredient.new(name: 'Egg', quantity: 2),
|
417
|
+
Ingredient.new(name: 'Cheese', quantity: 'a lot!')
|
418
|
+
]
|
419
|
+
```
|
420
|
+
Just call `#represent_with(&block)` and start nesting other inline representations.
|
421
|
+
```ruby
|
422
|
+
omelette.represent_with do
|
423
|
+
prop :title
|
424
|
+
inline :ingredients do
|
425
|
+
prop :name
|
426
|
+
prop :quantity
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
=> {
|
431
|
+
title: 'Omelette Du Fromage',
|
432
|
+
ingredients: [
|
433
|
+
{ name: 'Egg', quantity: 2 },
|
434
|
+
{ name: 'Cheese', quantity: 'a lot!' }
|
435
|
+
]
|
436
|
+
}
|
437
|
+
```
|
438
|
+
|
439
|
+
Inline representations can become lengthy and confusing over time.
|
440
|
+
If you find yourself nesting more than two levels or including more than 3 or 4 fields per level consider creating Representers with `Kashmir::Dsl`.
|
441
|
+
|
442
|
+
### `Kashmir::ActiveRecord`
|
443
|
+
Kashmir works just as well with ActiveRecord. `ActiveRecord::Relation`s can be used as Kashmir representations just as any other classes.
|
444
|
+
|
445
|
+
Kashmir will attempt to preload every `ActiveRecord::Relation` defined as representations automatically by using `ActiveRecord::Associations::Preloader`. This will guarantee that you don't run into N+1 queries while representing collections and dependent objects.
|
446
|
+
|
447
|
+
Here's an example of how Kashmir will attempt to optimize database queries:
|
448
|
+
|
449
|
+
```ruby
|
450
|
+
ActiveRecord::Schema.define do
|
451
|
+
create_table :recipes, force: true do |t|
|
452
|
+
t.column :title, :string
|
453
|
+
t.column :num_steps, :integer
|
454
|
+
t.column :chef_id, :integer
|
455
|
+
end
|
456
|
+
|
457
|
+
create_table :chefs, force: true do |t|
|
458
|
+
t.column :name, :string
|
459
|
+
end
|
460
|
+
end
|
461
|
+
```
|
462
|
+
```ruby
|
463
|
+
module AR
|
464
|
+
class Recipe < ActiveRecord::Base
|
465
|
+
include Kashmir
|
466
|
+
|
467
|
+
belongs_to :chef
|
468
|
+
|
469
|
+
representations do
|
470
|
+
rep :title
|
471
|
+
rep :chef
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
class Chef < ActiveRecord::Base
|
476
|
+
include Kashmir
|
477
|
+
|
478
|
+
has_many :recipes
|
479
|
+
|
480
|
+
representations do
|
481
|
+
rep :name
|
482
|
+
rep :recipes
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
```
|
487
|
+
|
488
|
+
```ruby
|
489
|
+
AR::Chef.all.each do |chef|
|
490
|
+
chef.recipes.to_a
|
491
|
+
end
|
492
|
+
```
|
493
|
+
will generate
|
494
|
+
```sql
|
495
|
+
SELECT * FROM chefs
|
496
|
+
SELECT "recipes".* FROM "recipes" WHERE "recipes"."chef_id" = ?
|
497
|
+
SELECT "recipes".* FROM "recipes" WHERE "recipes"."chef_id" = ?
|
498
|
+
```
|
499
|
+
|
500
|
+
With Kashmir:
|
501
|
+
```ruby
|
502
|
+
AR::Chef.all.represent([:recipes])
|
503
|
+
```
|
504
|
+
```sql
|
505
|
+
SELECT "chefs".* FROM "chefs"
|
506
|
+
SELECT "recipes".* FROM "recipes" WHERE "recipes"."chef_id" IN (1, 2)
|
507
|
+
```
|
508
|
+
|
509
|
+
For more examples, check out: https://github.com/IFTTT/kashmir/blob/master/test/activerecord_tricks_test.rb
|
510
|
+
|
511
|
+
### `Kashmir::Caching` (Experimental)
|
512
|
+
Caching is the best feature in Kashmir.
|
513
|
+
The `Kashmir::Caching` module will cache every level of the dependency tree Kashmir generates when representing an object.
|
514
|
+
|
515
|
+
![Dependency Tree](http://imageshack.com/a/img661/6152/LAGBwO.png "Dependency Tree")
|
516
|
+
|
517
|
+
As you can see in the image above, Kashmir will build a dependency tree of the representation.
|
518
|
+
If you have Caching on, Kashmir will:
|
519
|
+
|
520
|
+
- Build a cache key for each individual object (green)
|
521
|
+
- Wrap complex dependencies into their on cache key (blue and pink)
|
522
|
+
- Wrap the whole representation into one unique cache key (red)
|
523
|
+
|
524
|
+
Each layer gets its own cache keys which can be expired at different times.
|
525
|
+
Kashmir will also be able to fill in blanks in the dependency tree and fetch missing objects individually.
|
526
|
+
|
527
|
+
Caching is turned off by default, but you can use one of the two available implementations.
|
528
|
+
|
529
|
+
- [In Memory Caching] https://github.com/IFTTT/kashmir/blob/master/lib/kashmir/plugins/memory_caching.rb
|
530
|
+
- [Memcached] https://github.com/IFTTT/kashmir/blob/master/lib/kashmir/plugins/memcached_caching.rb (Currently used on ifttt.com).
|
531
|
+
|
532
|
+
You can also build your own custom caching engine by following the `NullCaching` protocol available at:
|
533
|
+
https://github.com/IFTTT/kashmir/blob/master/lib/kashmir/plugins/null_caching.rb
|
534
|
+
|
535
|
+
#### Enabling `Kashmir::Caching`
|
536
|
+
##### In Memory
|
537
|
+
```ruby
|
538
|
+
Kashmir.init(
|
539
|
+
cache_client: Kashmir::Caching::Memory.new
|
540
|
+
)
|
541
|
+
```
|
542
|
+
|
543
|
+
##### With Memcached
|
544
|
+
```ruby
|
545
|
+
client = Dalli::Client.new(url, namespace: 'kashmir', compress: true)
|
546
|
+
default_ttl = 5.minutes
|
547
|
+
|
548
|
+
Kashmir.init(
|
549
|
+
cache_client: Kashmir::Caching::Memcached.new(client, default_ttl)
|
550
|
+
)
|
551
|
+
```
|
552
|
+
|
553
|
+
For more advanced examples, check out: https://github.com/IFTTT/kashmir/blob/master/test/caching_test.rb
|
554
|
+
|
555
|
+
## Contributing
|
556
|
+
|
557
|
+
1. Fork it ( https://github.com/[my-github-username]/kashmir/fork )
|
558
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
559
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
560
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
561
|
+
5. Create a new Pull Request
|
562
|
+
|
563
|
+
|