kashmir 0.1
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 +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
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Kashmir::InlineDsl do
|
4
|
+
|
5
|
+
module InlineDSLTesting
|
6
|
+
class Recipe < OpenStruct
|
7
|
+
include Kashmir
|
8
|
+
|
9
|
+
representations do
|
10
|
+
rep(:num_steps)
|
11
|
+
rep(:title)
|
12
|
+
rep(:chef)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Chef < OpenStruct
|
17
|
+
include Kashmir
|
18
|
+
|
19
|
+
representations do
|
20
|
+
rep(:full_name)
|
21
|
+
rep(:award)
|
22
|
+
end
|
23
|
+
|
24
|
+
def full_name
|
25
|
+
"#{first_name} #{last_name}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Award < OpenStruct
|
30
|
+
include Kashmir
|
31
|
+
|
32
|
+
representations do
|
33
|
+
base([:name, :year])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
before do
|
39
|
+
@award = InlineDSLTesting::Award.new(name: 'Best Chef', year: 2015)
|
40
|
+
@chef = InlineDSLTesting::Chef.new(first_name: 'Netto', last_name: 'Farah', award: @award)
|
41
|
+
@brisket = InlineDSLTesting::Recipe.new(title: 'BBQ Brisket', num_steps: 2, chef: @chef)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'creates an inline representer' do
|
45
|
+
inline_representer = Kashmir::InlineDsl.build do
|
46
|
+
prop :num_steps
|
47
|
+
prop :title
|
48
|
+
end
|
49
|
+
|
50
|
+
assert_equal inline_representer.definitions, [:num_steps, :title]
|
51
|
+
|
52
|
+
cooked_brisket = @brisket.represent(inline_representer.definitions)
|
53
|
+
assert_equal cooked_brisket, { title: 'BBQ Brisket', num_steps: 2 }
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'Reduced syntax' do
|
57
|
+
|
58
|
+
describe '#represent_with' do
|
59
|
+
it 'works with flat representers' do
|
60
|
+
cooked_brisket = @brisket.represent_with do
|
61
|
+
prop :title
|
62
|
+
prop :num_steps
|
63
|
+
end
|
64
|
+
|
65
|
+
assert_equal cooked_brisket, { title: 'BBQ Brisket', num_steps: 2 }
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'works with nested representers' do
|
69
|
+
cooked_brisket = @brisket.represent_with do
|
70
|
+
prop :title
|
71
|
+
prop :num_steps
|
72
|
+
|
73
|
+
inline :chef do
|
74
|
+
prop :full_name
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
assert_equal cooked_brisket, {
|
79
|
+
title: 'BBQ Brisket',
|
80
|
+
num_steps: 2,
|
81
|
+
chef: {
|
82
|
+
full_name: 'Netto Farah'
|
83
|
+
}
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'works with multi level nested representers' do
|
89
|
+
cooked_brisket = @brisket.represent_with do
|
90
|
+
prop :title
|
91
|
+
inline :chef do
|
92
|
+
prop :full_name
|
93
|
+
inline :award
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
assert_equal cooked_brisket, {
|
98
|
+
title: 'BBQ Brisket',
|
99
|
+
chef: {
|
100
|
+
full_name: 'Netto Farah',
|
101
|
+
award: {
|
102
|
+
name: 'Best Chef', year: 2015
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Kashmir do
|
4
|
+
|
5
|
+
class Recipe < OpenStruct
|
6
|
+
include Kashmir
|
7
|
+
|
8
|
+
representations do
|
9
|
+
base([:title, :preparation_time])
|
10
|
+
rep(:num_steps)
|
11
|
+
rep(:chef)
|
12
|
+
end
|
13
|
+
|
14
|
+
def num_steps
|
15
|
+
steps.size
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Chef < OpenStruct
|
20
|
+
include Kashmir
|
21
|
+
|
22
|
+
representations do
|
23
|
+
base([:name])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
before(:each) do
|
28
|
+
@recipe = Recipe.new(title: 'Beef stew', preparation_time: 60)
|
29
|
+
@chef = Chef.new(name: 'Netto')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'renders basic attribute representations' do
|
33
|
+
assert_equal @recipe.represent, { title: 'Beef stew', preparation_time: 60 }
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'renders basic calculated representations' do
|
37
|
+
@recipe.steps = ['chop', 'cook']
|
38
|
+
assert_equal @recipe.represent([:num_steps]), {
|
39
|
+
title: 'Beef stew',
|
40
|
+
preparation_time: 60,
|
41
|
+
num_steps: 2
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'renders nested representations' do
|
46
|
+
@recipe.chef = @chef
|
47
|
+
representation = @recipe.represent([:chef])
|
48
|
+
assert_equal representation[:chef], { name: 'Netto' }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'Complex Representations' do
|
53
|
+
class BBQRecipe < OpenStruct
|
54
|
+
include Kashmir
|
55
|
+
|
56
|
+
representations do
|
57
|
+
base([:title])
|
58
|
+
rep(:chef)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class BBQChef < OpenStruct
|
63
|
+
include Kashmir
|
64
|
+
|
65
|
+
representations do
|
66
|
+
base([:name])
|
67
|
+
rep(:restaurant)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class BBQRestaurant < OpenStruct
|
72
|
+
include Kashmir
|
73
|
+
|
74
|
+
representations do
|
75
|
+
base([:name])
|
76
|
+
rep(:rating)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
before do
|
81
|
+
@bbq_joint = BBQRestaurant.new(name: "Netto's BBQ Joint", rating: '5 stars')
|
82
|
+
@netto = BBQChef.new(name: 'Netto', restaurant: @bbq_joint)
|
83
|
+
@brisket = BBQRecipe.new(title: 'BBQ Brisket', chef: @netto)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'works with base representations' do
|
87
|
+
assert_equal @netto.represent, { name: 'Netto' }
|
88
|
+
assert_equal @brisket.represent, { title: 'BBQ Brisket' }
|
89
|
+
assert_equal @bbq_joint.represent, { name: "Netto's BBQ Joint" }
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'works with simple nesting' do
|
93
|
+
representation = @netto.represent([:restaurant])
|
94
|
+
assert_equal representation, {
|
95
|
+
name: 'Netto',
|
96
|
+
restaurant: { name: "Netto's BBQ Joint" }
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'allows clients to customize nested representations' do
|
101
|
+
representation = @netto.represent [{ :restaurant => [:rating] }]
|
102
|
+
|
103
|
+
assert_equal representation, {
|
104
|
+
name: 'Netto',
|
105
|
+
restaurant: { name: "Netto's BBQ Joint", rating: '5 stars' }
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'allows clients to create complex representation trees' do
|
110
|
+
representation = @brisket.represent([
|
111
|
+
:chef => [
|
112
|
+
{ :restaurant => [ :rating ] }
|
113
|
+
]
|
114
|
+
])
|
115
|
+
|
116
|
+
assert_equal representation, {
|
117
|
+
title: 'BBQ Brisket',
|
118
|
+
chef: {
|
119
|
+
name: 'Netto',
|
120
|
+
restaurant: {
|
121
|
+
name: "Netto's BBQ Joint",
|
122
|
+
rating: '5 stars'
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe 'Collections' do
|
130
|
+
|
131
|
+
class Ingredient < OpenStruct
|
132
|
+
include Kashmir
|
133
|
+
|
134
|
+
representations do
|
135
|
+
rep(:name)
|
136
|
+
rep(:quantity)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class ClassyRecipe < OpenStruct
|
141
|
+
include Kashmir
|
142
|
+
|
143
|
+
representations do
|
144
|
+
rep(:title)
|
145
|
+
rep(:ingredients)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
before(:each) do
|
150
|
+
@omelette = ClassyRecipe.new(title: 'Omelette Du Fromage')
|
151
|
+
@omelette.ingredients = [
|
152
|
+
Ingredient.new(name: 'Egg', quantity: 2),
|
153
|
+
Ingredient.new(name: 'Cheese', quantity: 'a lot!')
|
154
|
+
]
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'represents collections' do
|
158
|
+
nice_omelette = @omelette.represent([:title, { :ingredients => [ :name, :quantity ]}])
|
159
|
+
|
160
|
+
assert_equal nice_omelette, {
|
161
|
+
title: 'Omelette Du Fromage',
|
162
|
+
ingredients: [
|
163
|
+
{ name: 'Egg', quantity: 2 },
|
164
|
+
{ name: 'Cheese', quantity: 'a lot!' }
|
165
|
+
]
|
166
|
+
}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
describe "Hashes" do
|
171
|
+
|
172
|
+
class RecipeCategories < OpenStruct
|
173
|
+
include Kashmir
|
174
|
+
|
175
|
+
representations do
|
176
|
+
rep :categories
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
before(:each) do
|
181
|
+
@omelette = ClassyRecipe.new(title: 'Omelette Du Fromage')
|
182
|
+
@scrambled_eggs = ClassyRecipe.new(title: 'Scrambled Eggs')
|
183
|
+
|
184
|
+
@brisket = ClassyRecipe.new(title: 'Briksket')
|
185
|
+
@ribs = ClassyRecipe.new(title: 'BBQ Ribs')
|
186
|
+
|
187
|
+
@categories = [
|
188
|
+
{ name: 'Breakfast', recipes: [ @omelette, @scrambled_eggs] },
|
189
|
+
{ name: 'Dinner', recipes: [ @brisket, @ribs ] }
|
190
|
+
]
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'represents hashes' do
|
194
|
+
representation = RecipeCategories.new(categories: @categories).represent([{
|
195
|
+
categories: [
|
196
|
+
:name,
|
197
|
+
recipes: [:title]
|
198
|
+
]
|
199
|
+
}])
|
200
|
+
|
201
|
+
assert_equal representation, {
|
202
|
+
categories: [
|
203
|
+
{
|
204
|
+
name: "Breakfast", recipes: [
|
205
|
+
{ title: "Omelette Du Fromage" }, { title: "Scrambled Eggs" }
|
206
|
+
]
|
207
|
+
},
|
208
|
+
{
|
209
|
+
name: "Dinner", recipes: [
|
210
|
+
{ title: "Briksket" }, { title: "BBQ Ribs" }
|
211
|
+
]
|
212
|
+
}
|
213
|
+
]
|
214
|
+
}
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module AR
|
2
|
+
class Recipe < ActiveRecord::Base
|
3
|
+
include Kashmir
|
4
|
+
|
5
|
+
belongs_to :chef
|
6
|
+
has_many :recipes_ingredients
|
7
|
+
has_many :ingredients, through: :recipes_ingredients
|
8
|
+
|
9
|
+
representations do
|
10
|
+
rep :title
|
11
|
+
rep :chef
|
12
|
+
rep :ingredients
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Ingredient < ActiveRecord::Base
|
17
|
+
include Kashmir
|
18
|
+
|
19
|
+
representations do
|
20
|
+
rep :name
|
21
|
+
rep :quantity
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class RecipesIngredient < ActiveRecord::Base
|
26
|
+
belongs_to :recipe
|
27
|
+
belongs_to :ingredient
|
28
|
+
end
|
29
|
+
|
30
|
+
class Restaurant < ActiveRecord::Base
|
31
|
+
include Kashmir
|
32
|
+
|
33
|
+
belongs_to :owner, class_name: 'Chef'
|
34
|
+
has_one :rating
|
35
|
+
|
36
|
+
representations do
|
37
|
+
rep :name
|
38
|
+
rep :owner
|
39
|
+
rep :rating
|
40
|
+
|
41
|
+
rep :current_customer_count, cacheable: false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Rating < ActiveRecord::Base
|
46
|
+
include Kashmir
|
47
|
+
|
48
|
+
belongs_to :restaurant
|
49
|
+
|
50
|
+
representations do
|
51
|
+
rep :value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Chef < ActiveRecord::Base
|
56
|
+
include Kashmir
|
57
|
+
|
58
|
+
has_many :recipes
|
59
|
+
has_many :ingredients, through: :recipes
|
60
|
+
has_one :restaurant, foreign_key: 'owner_id'
|
61
|
+
|
62
|
+
representations do
|
63
|
+
rep :name
|
64
|
+
rep :recipes
|
65
|
+
rep :ingredients
|
66
|
+
rep :restaurant
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module TestData
|
2
|
+
def self.create_tom
|
3
|
+
AR::Chef.create(name: 'Tom').tap do |tom|
|
4
|
+
AR::Recipe.create(title: 'Pastrami Sandwich', chef: tom).tap do |r|
|
5
|
+
r.ingredients.create(name: 'Pastrami', quantity: 'a lot')
|
6
|
+
r.ingredients.create(name: 'Cheese', quantity: '1 slice')
|
7
|
+
end
|
8
|
+
|
9
|
+
AR::Recipe.create(title: 'Belly Burger', chef: tom).tap do |r|
|
10
|
+
r.ingredients.create(name: 'Pork Belly', quantity: 'plenty')
|
11
|
+
r.ingredients.create(name: 'Green Apple', quantity: '2 slices')
|
12
|
+
end
|
13
|
+
|
14
|
+
AR::Restaurant.create(name: 'Chef Tom Belly Burgers', owner: tom) do |res|
|
15
|
+
res.create_rating(value: '3 stars')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.create_netto
|
21
|
+
AR::Chef.create(name: 'Netto').tap do |netto|
|
22
|
+
AR::Recipe.create(title: 'Turkey Sandwich', chef: netto).tap do |r|
|
23
|
+
r.ingredients.create(name: 'Turkey', quantity: 'a lot')
|
24
|
+
r.ingredients.create(name: 'Cheese', quantity: '1 slice')
|
25
|
+
end
|
26
|
+
|
27
|
+
AR::Recipe.create(title: 'Cheese Burger', chef: netto).tap do |r|
|
28
|
+
r.ingredients.create(name: 'Patty', quantity: '1')
|
29
|
+
r.ingredients.create(name: 'Cheese', quantity: '2 slices')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
create_table :recipes, force: true do |t|
|
3
|
+
t.column :title, :string
|
4
|
+
t.column :num_steps, :integer
|
5
|
+
t.column :chef_id, :integer
|
6
|
+
end
|
7
|
+
|
8
|
+
create_table :ingredients, force: true do |t|
|
9
|
+
t.column :name, :string
|
10
|
+
t.column :quantity, :string
|
11
|
+
end
|
12
|
+
|
13
|
+
create_table :recipes_ingredients, force: true do |t|
|
14
|
+
t.column :recipe_id, :integer
|
15
|
+
t.column :ingredient_id, :integer
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table :chefs, force: true do |t|
|
19
|
+
t.column :name, :string
|
20
|
+
end
|
21
|
+
|
22
|
+
create_table :restaurants, force: true do |t|
|
23
|
+
t.column :name, :string
|
24
|
+
t.column :owner_id, :integer
|
25
|
+
t.column :current_customer_count, :integer
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table :ratings, force: true do |t|
|
29
|
+
t.column :value, :string
|
30
|
+
t.column :restaurant_id, :integer
|
31
|
+
end
|
32
|
+
end
|