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
@@ -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
|