spyke 1.0.0
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/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +84 -0
- data/Rakefile +9 -0
- data/lib/spyke.rb +6 -0
- data/lib/spyke/associations.rb +55 -0
- data/lib/spyke/associations/association.rb +62 -0
- data/lib/spyke/associations/belongs_to.rb +11 -0
- data/lib/spyke/associations/has_many.rb +39 -0
- data/lib/spyke/associations/has_one.rb +11 -0
- data/lib/spyke/attributes.rb +130 -0
- data/lib/spyke/base.rb +25 -0
- data/lib/spyke/collection.rb +10 -0
- data/lib/spyke/config.rb +5 -0
- data/lib/spyke/exceptions.rb +3 -0
- data/lib/spyke/http.rb +78 -0
- data/lib/spyke/orm.rb +83 -0
- data/lib/spyke/path.rb +28 -0
- data/lib/spyke/relation.rb +57 -0
- data/lib/spyke/result.rb +25 -0
- data/lib/spyke/scope_registry.rb +17 -0
- data/lib/spyke/scopes.rb +30 -0
- data/lib/spyke/version.rb +3 -0
- data/spyke.gemspec +36 -0
- data/test/associations_test.rb +282 -0
- data/test/attributes_test.rb +102 -0
- data/test/callbacks_test.rb +24 -0
- data/test/custom_request_test.rb +53 -0
- data/test/orm_test.rb +136 -0
- data/test/path_test.rb +23 -0
- data/test/relation_test.rb +76 -0
- data/test/support/api.rb +6 -0
- data/test/support/fixtures.rb +65 -0
- data/test/support/webmock.rb +6 -0
- data/test/test_helper.rb +14 -0
- metadata +287 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Spyke
|
4
|
+
class AttributesTest < MiniTest::Test
|
5
|
+
def test_predicate_methods
|
6
|
+
stub_request(:get, 'http://sushi.com/recipes/1').to_return_json(data: { id: 1, title: 'Sushi' })
|
7
|
+
|
8
|
+
recipe = Recipe.find(1)
|
9
|
+
|
10
|
+
assert_equal true, recipe.title?
|
11
|
+
assert_equal false, recipe.description?
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_respond_to
|
15
|
+
stub_request(:get, 'http://sushi.com/recipes/1').to_return_json(data: { id: 1, serves: 3 })
|
16
|
+
|
17
|
+
recipe = Recipe.find(1)
|
18
|
+
|
19
|
+
assert_equal true, recipe.respond_to?(:serves)
|
20
|
+
assert_equal false, recipe.respond_to?(:story)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_assigning_attributes
|
24
|
+
recipe = Recipe.new(id: 2)
|
25
|
+
recipe.attributes = { title: 'Pasta' }
|
26
|
+
|
27
|
+
assert_equal 'Pasta', recipe.title
|
28
|
+
assert_equal 2, recipe.id
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_removing_id_if_blank_string
|
32
|
+
recipe = Recipe.new
|
33
|
+
|
34
|
+
assert_nil recipe.id
|
35
|
+
assert_equal({ 'recipe' => {} }, recipe.to_params)
|
36
|
+
|
37
|
+
recipe.id = ''
|
38
|
+
assert_nil recipe.id
|
39
|
+
assert_equal({ 'recipe' => {} }, recipe.to_params)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_setters
|
43
|
+
recipe = Recipe.new
|
44
|
+
recipe.title = 'Sushi'
|
45
|
+
assert_equal 'Sushi', recipe.title
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_equality
|
49
|
+
assert_equal Recipe.new(id: 2, title: 'Fish'), Recipe.new(id: 2, title: 'Fish')
|
50
|
+
refute_equal Recipe.new(id: 2, title: 'Fish'), Recipe.new(id: 1, title: 'Fish')
|
51
|
+
refute_equal Recipe.new(id: 2, title: 'Fish'), 'not_a_spyke_object'
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_explicit_attributes
|
55
|
+
recipe = Recipe.new
|
56
|
+
assert_equal nil, recipe.title
|
57
|
+
assert_raises NoMethodError do
|
58
|
+
recipe.description
|
59
|
+
end
|
60
|
+
|
61
|
+
recipe = Recipe.new(title: 'Fish')
|
62
|
+
assert_equal 'Fish', recipe.title
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_converting_files_to_faraday_io
|
66
|
+
Faraday::UploadIO.stubs(:new).with('/photo.jpg', 'image/jpeg').returns('UploadIO')
|
67
|
+
file = mock
|
68
|
+
file.stubs(:path).returns('/photo.jpg')
|
69
|
+
file.stubs(:content_type).returns('image/jpeg')
|
70
|
+
|
71
|
+
recipe = Recipe.new(image: Image.new(file: file))
|
72
|
+
|
73
|
+
assert_equal 'UploadIO', recipe.image.to_params['image']['file']
|
74
|
+
assert_equal 'UploadIO', recipe.to_params['recipe']['image']['file']
|
75
|
+
|
76
|
+
recipe = Recipe.new(image_attributes: { file: file })
|
77
|
+
|
78
|
+
assert_equal 'UploadIO', recipe.image.to_params['image']['file']
|
79
|
+
assert_equal 'UploadIO', recipe.to_params['recipe']['image']['file']
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_inspect
|
83
|
+
recipe = Recipe.new(id: 2, title: 'Pizza', description: 'Delicious')
|
84
|
+
assert_equal '#<Recipe(/recipes/2) id: 2 title: "Pizza" description: "Delicious">', recipe.inspect
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_rejecting_wrong_number_of_args
|
88
|
+
skip 'wishlisted'
|
89
|
+
stub_request(:any, /.*/)
|
90
|
+
recipe = Recipe.new(description: 'Delicious')
|
91
|
+
assert_raises ArgumentError do
|
92
|
+
recipe.description(2)
|
93
|
+
end
|
94
|
+
assert_raises ArgumentError do
|
95
|
+
recipe.description?(2)
|
96
|
+
end
|
97
|
+
assert_raises ArgumentError do
|
98
|
+
recipe.image(2)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Spyke
|
4
|
+
class CallbacksTest < MiniTest::Test
|
5
|
+
def setup
|
6
|
+
stub_request(:any, /.*/)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_before_save
|
10
|
+
Recipe.any_instance.expects(:before_save_callback)
|
11
|
+
Recipe.create
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_before_create
|
15
|
+
Recipe.any_instance.expects(:before_create_callback)
|
16
|
+
Recipe.create
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_before_update
|
20
|
+
Recipe.any_instance.expects(:before_update_callback)
|
21
|
+
Recipe.new(id: 1).save
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Spyke
|
4
|
+
class CustomRequestTest < MiniTest::Test
|
5
|
+
def test_custom_get_request_from_class
|
6
|
+
endpoint = stub_request(:get, 'http://sushi.com/recipes/recent').to_return_json(data: [{ id: 1, title: 'Bread' }])
|
7
|
+
assert_equal %w{ Bread }, Recipe.get('/recipes/recent').map(&:title)
|
8
|
+
assert_requested endpoint
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_get_request_with_prepended_scope
|
12
|
+
skip 'wishlisted'
|
13
|
+
endpoint = stub_request(:get, 'http://sushi.com/recipes/recent?status=published')
|
14
|
+
Recipe.published.get('/recipes/recent')
|
15
|
+
assert_requested endpoint
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_get_request_with_appended_scope
|
19
|
+
skip 'wishlisted'
|
20
|
+
endpoint = stub_request(:get, 'http://sushi.com/recipes/recent?status=published')
|
21
|
+
Recipe.get('/recipes/recent').published.fetch
|
22
|
+
assert_requested endpoint
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_custom_get_request_from_class
|
26
|
+
endpoint = stub_request(:get, 'http://sushi.com/recipes/recent').to_return_json(data: [{ id: 1, title: 'Bread' }])
|
27
|
+
assert_equal %w{ Bread }, Recipe.get('/recipes/recent').map(&:title)
|
28
|
+
assert_requested endpoint
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_custom_put_request_from_class
|
32
|
+
endpoint = stub_request(:put, 'http://sushi.com/recipes/1/publish')
|
33
|
+
Recipe.put('/recipes/1/publish')
|
34
|
+
assert_requested endpoint
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_custom_put_request_from_instance
|
38
|
+
endpoint = stub_request(:put, 'http://sushi.com/recipes/1/publish').to_return_json(data: { id: 1, status: 'published' })
|
39
|
+
recipe = Recipe.new(id: 1, status: 'unpublished')
|
40
|
+
recipe.put('/recipes/:id/publish')
|
41
|
+
|
42
|
+
assert_equal 'published', recipe.status
|
43
|
+
assert_requested endpoint
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_custom_put_request_from_instance_with_symbol
|
47
|
+
endpoint = stub_request(:put, 'http://sushi.com/recipes/1/draft')
|
48
|
+
recipe = Recipe.new(id: 1)
|
49
|
+
recipe.put(:draft)
|
50
|
+
assert_requested endpoint
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/test/orm_test.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Spyke
|
4
|
+
class OrmTest < MiniTest::Test
|
5
|
+
def test_find
|
6
|
+
stub_request(:get, 'http://sushi.com/recipes/1').to_return_json(data: { id: 1, title: 'Sushi' })
|
7
|
+
stub_request(:get, 'http://sushi.com/users/1').to_return_json(data: { id: 1, name: 'Bob' })
|
8
|
+
|
9
|
+
recipe = Recipe.find(1)
|
10
|
+
user = User.find(1)
|
11
|
+
|
12
|
+
assert_equal 1, recipe.id
|
13
|
+
assert_equal 'Sushi', recipe.title
|
14
|
+
assert_equal 'Bob', user.name
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_reload
|
18
|
+
stub_request(:get, 'http://sushi.com/recipes/1').to_return_json(data: { id: 1, title: 'Sushi' })
|
19
|
+
|
20
|
+
recipe = Recipe.find(1)
|
21
|
+
assert_equal 'Sushi', recipe.title
|
22
|
+
|
23
|
+
stub_request(:get, 'http://sushi.com/recipes/1').to_return_json(data: { id: 1, title: 'Sashimi' })
|
24
|
+
recipe.reload
|
25
|
+
assert_equal 'Sashimi', recipe.title
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_find_with_slug
|
29
|
+
endpoint = stub_request(:get, 'http://sushi.com/recipes/1').to_return_json(data: { id: 1 })
|
30
|
+
Recipe.find('1-delicious-soup')
|
31
|
+
assert_requested endpoint
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_404
|
35
|
+
stub_request(:get, 'http://sushi.com/recipes/1').to_return(status: 404, body: { message: 'Not found' }.to_json)
|
36
|
+
|
37
|
+
assert_raises ResourceNotFound do
|
38
|
+
Recipe.find(1)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_save_new_record
|
43
|
+
endpoint = stub_request(:post, 'http://sushi.com/recipes').with(body: { recipe: { title: 'Sushi' } }).to_return_json(data: { id: 1, title: 'Sushi (created)' })
|
44
|
+
|
45
|
+
recipe = Recipe.new(title: 'Sushi')
|
46
|
+
recipe.save
|
47
|
+
|
48
|
+
assert_equal 'Sushi (created)', recipe.title
|
49
|
+
assert_requested endpoint
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_save_persisted_record
|
53
|
+
stub_request(:put, /.*/)
|
54
|
+
endpoint = stub_request(:put, 'http://sushi.com/recipes/1').with(body: { recipe: { title: 'Sushi' } }).to_return_json(data: { id: 1, title: 'Sushi (saved)' })
|
55
|
+
|
56
|
+
recipe = Recipe.new(id: 1, title: 'Sashimi')
|
57
|
+
recipe.title = 'Sushi'
|
58
|
+
recipe.save
|
59
|
+
|
60
|
+
assert_equal 'Sushi (saved)', recipe.title
|
61
|
+
assert_requested endpoint
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_create
|
65
|
+
endpoint = stub_request(:post, 'http://sushi.com/recipes').with(body: { recipe: { title: 'Sushi' } }).to_return_json(data: { id: 1, title: 'Sushi' })
|
66
|
+
|
67
|
+
recipe = Recipe.create(title: 'Sushi')
|
68
|
+
|
69
|
+
assert_equal 'Sushi', recipe.title
|
70
|
+
assert_requested endpoint
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_find_using_custom_uri_template
|
74
|
+
endpoint = stub_request(:get, 'http://sushi.com/images/photos/1').to_return_json(data: { id: 1 })
|
75
|
+
Photo.find(1)
|
76
|
+
assert_requested endpoint
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_create_using_custom_uri_template
|
80
|
+
endpoint = stub_request(:post, 'http://sushi.com/images/photos')
|
81
|
+
Photo.create
|
82
|
+
assert_requested endpoint
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_create_using_nested_custom_uri_template
|
86
|
+
endpoint = stub_request(:post, 'http://sushi.com/recipes/1/ingredients')
|
87
|
+
Ingredient.new(recipe_id: 1).save
|
88
|
+
assert_requested endpoint
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_create_using_custom_method
|
92
|
+
endpoint = stub_request(:put, 'http://sushi.com/images')
|
93
|
+
Image.create
|
94
|
+
assert_requested endpoint
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_inheritance_using_custom_method
|
98
|
+
endpoint = stub_request(:put, 'http://sushi.com/step_images')
|
99
|
+
StepImage.create
|
100
|
+
assert_requested endpoint
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_non_nested_params
|
104
|
+
assert_equal({ 'url' => 'bob.jpg' }, RecipeImage.new(url: 'bob.jpg').to_params)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_destroy
|
108
|
+
endpoint = stub_request(:delete, 'http://sushi.com/recipes/1')
|
109
|
+
Recipe.new(id: 1).destroy
|
110
|
+
assert_requested endpoint
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_destroy_class_method
|
114
|
+
endpoint = stub_request(:delete, 'http://sushi.com/recipes/1')
|
115
|
+
Recipe.destroy(1)
|
116
|
+
assert_requested endpoint
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_scoped_destroy_class_method
|
120
|
+
endpoint = stub_request(:delete, 'http://sushi.com/recipes/1/ingredients/2')
|
121
|
+
Ingredient.where(recipe_id: 1).destroy(2)
|
122
|
+
assert_requested endpoint
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_scoped_destroy_class_method_without_param
|
126
|
+
endpoint = stub_request(:delete, 'http://sushi.com/recipes/1/image')
|
127
|
+
RecipeImage.where(recipe_id: 1).destroy
|
128
|
+
assert_requested endpoint
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_validations
|
132
|
+
assert_equal false, RecipeImage.new.valid?
|
133
|
+
assert_equal true, RecipeImage.new(url: 'bob.jpg').valid?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/test/path_test.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Spyke
|
4
|
+
class PathTest < MiniTest::Test
|
5
|
+
def test_collection_path
|
6
|
+
assert_equal '/recipes', Path.new('/recipes/:id').to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_resource_path
|
10
|
+
assert_equal '/recipes/2', Path.new('/recipes/:id', id: 2).to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_nested_collection_path
|
14
|
+
path = Path.new('/users/:user_id/recipes/:id', user_id: 1, status: 'published')
|
15
|
+
assert_equal [:user_id, :id], path.variables
|
16
|
+
assert_equal '/users/1/recipes', path.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_nested_resource_path
|
20
|
+
assert_equal '/users/1/recipes/2', Path.new('/users/:user_id/recipes/:id', user_id: 1, id: 2).to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Spyke
|
4
|
+
class RelationTest < MiniTest::Test
|
5
|
+
def test_all
|
6
|
+
stub_request(:get, 'http://sushi.com/recipes').to_return_json(data: [{ id: 1, title: 'Sushi' }, { id: 2, title: 'Nigiri' }], metadata: 'meta')
|
7
|
+
|
8
|
+
recipes = Recipe.all
|
9
|
+
|
10
|
+
assert_equal %w{ Sushi Nigiri }, recipes.map(&:title)
|
11
|
+
assert_equal [1, 2], recipes.map(&:id)
|
12
|
+
assert_equal 'meta', recipes.metadata
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_chainable_where
|
16
|
+
endpoint = stub_request(:get, 'http://sushi.com/recipes?status=published&per_page=3')
|
17
|
+
|
18
|
+
Recipe.where(status: 'published').where(per_page: 3).to_a
|
19
|
+
|
20
|
+
assert_requested endpoint
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_chainable_class_method
|
24
|
+
endpoint = stub_request(:get, 'http://sushi.com/recipes?status=published&per_page=3')
|
25
|
+
|
26
|
+
Recipe.where(per_page: 3).published.to_a
|
27
|
+
|
28
|
+
assert_requested endpoint
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_prepended_chainable_class_method
|
32
|
+
endpoint = stub_request(:get, 'http://sushi.com/recipes?status=published&per_page=3')
|
33
|
+
|
34
|
+
Recipe.published.where(per_page: 3).to_a
|
35
|
+
|
36
|
+
assert_requested endpoint
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_scope_class_method
|
40
|
+
endpoint = stub_request(:get, 'http://sushi.com/recipes?status=published&page=3')
|
41
|
+
|
42
|
+
Recipe.published.page(3).to_a
|
43
|
+
assert_requested endpoint
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_scope_doesnt_get_stuck
|
47
|
+
endpoint_1 = stub_request(:get, 'http://sushi.com/recipes?per_page=3&status=published')
|
48
|
+
endpoint_2 = stub_request(:get, 'http://sushi.com/recipes?status=published')
|
49
|
+
|
50
|
+
Recipe.where(status: 'published').where(per_page: 3).to_a
|
51
|
+
Recipe.where(status: 'published').to_a
|
52
|
+
assert_requested endpoint_1
|
53
|
+
assert_requested endpoint_2
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_create_scoped
|
57
|
+
endpoint = stub_request(:post, 'http://sushi.com/recipes').with(body: { recipe: { title: 'Sushi', status: 'published' } })
|
58
|
+
|
59
|
+
Recipe.published.create(title: 'Sushi')
|
60
|
+
|
61
|
+
assert_requested endpoint
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_cached_result
|
65
|
+
endpoint_1 = stub_request(:get, 'http://sushi.com/recipes?status=published&per_page=3')
|
66
|
+
endpoint_2 = stub_request(:get, 'http://sushi.com/recipes?status=published')
|
67
|
+
|
68
|
+
recipes = Recipe.published.where(per_page: 3)
|
69
|
+
recipes.any?
|
70
|
+
recipes.to_a
|
71
|
+
assert_requested endpoint_1, times: 1
|
72
|
+
Recipe.published.to_a
|
73
|
+
assert_requested endpoint_2, times: 1
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/test/support/api.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
class Recipe < Spyke::Base
|
2
|
+
has_many :groups
|
3
|
+
has_one :image
|
4
|
+
has_one :background_image, class_name: 'Image', uri: nil
|
5
|
+
has_one :alternate, class_name: 'Recipe', uri: '/recipes/:recipe_id/alternates/recipe'
|
6
|
+
belongs_to :user
|
7
|
+
|
8
|
+
scope :published, -> { where(status: 'published') }
|
9
|
+
attributes :title
|
10
|
+
|
11
|
+
before_save :before_save_callback
|
12
|
+
before_create :before_create_callback
|
13
|
+
before_update :before_update_callback
|
14
|
+
|
15
|
+
accepts_nested_attributes_for :image, :user, :groups
|
16
|
+
|
17
|
+
def self.page(number)
|
18
|
+
if number.present?
|
19
|
+
where(page: number)
|
20
|
+
else
|
21
|
+
all
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def ingredients
|
26
|
+
groups.first.ingredients
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def before_create_callback; end
|
32
|
+
def before_update_callback; end
|
33
|
+
def before_save_callback; end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Image < Spyke::Base
|
37
|
+
method_for :create, :put
|
38
|
+
end
|
39
|
+
|
40
|
+
class StepImage < Image
|
41
|
+
end
|
42
|
+
|
43
|
+
class RecipeImage < Image
|
44
|
+
uri '/recipes/:recipe_id/image'
|
45
|
+
validates :url, presence: true
|
46
|
+
attributes :url
|
47
|
+
include_root_in_json false
|
48
|
+
end
|
49
|
+
|
50
|
+
class Group < Spyke::Base
|
51
|
+
has_many :ingredients, uri: nil
|
52
|
+
accepts_nested_attributes_for :ingredients
|
53
|
+
end
|
54
|
+
|
55
|
+
class Ingredient < Spyke::Base
|
56
|
+
uri '/recipes/:recipe_id/ingredients/:id'
|
57
|
+
end
|
58
|
+
|
59
|
+
class User < Spyke::Base
|
60
|
+
has_many :recipes
|
61
|
+
end
|
62
|
+
|
63
|
+
class Photo < Spyke::Base
|
64
|
+
uri '/images/photos/:id'
|
65
|
+
end
|