kashmir 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kashmir.gemspec
4
+ gemspec
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
+