grape-roar 0.4.0 → 0.4.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 +4 -4
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/.rubocop.yml +6 -2
- data/.rubocop_todo.yml +38 -9
- data/.travis.yml +29 -3
- data/Appraisals +9 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +6 -3
- data/README.md +218 -0
- data/Rakefile +3 -1
- data/gemfiles/with_activerecord.gemfile +22 -0
- data/gemfiles/with_mongoid.gemfile +22 -0
- data/grape-roar.gemspec +3 -0
- data/lib/grape-roar.rb +2 -0
- data/lib/grape/roar.rb +3 -0
- data/lib/grape/roar/decorator.rb +2 -0
- data/lib/grape/roar/extensions.rb +3 -0
- data/lib/grape/roar/extensions/relations.rb +23 -0
- data/lib/grape/roar/extensions/relations/adapters.rb +22 -0
- data/lib/grape/roar/extensions/relations/adapters/active_record.rb +35 -0
- data/lib/grape/roar/extensions/relations/adapters/base.rb +49 -0
- data/lib/grape/roar/extensions/relations/adapters/mongoid.rb +38 -0
- data/lib/grape/roar/extensions/relations/dsl_methods.rb +86 -0
- data/lib/grape/roar/extensions/relations/exceptions.rb +14 -0
- data/lib/grape/roar/extensions/relations/mapper.rb +89 -0
- data/lib/grape/roar/extensions/relations/validations.rb +5 -0
- data/lib/grape/roar/extensions/relations/validations/active_record.rb +67 -0
- data/lib/grape/roar/extensions/relations/validations/misc.rb +18 -0
- data/lib/grape/roar/extensions/relations/validations/mongoid.rb +88 -0
- data/lib/grape/roar/formatter.rb +2 -0
- data/lib/grape/roar/representer.rb +2 -0
- data/lib/grape/roar/version.rb +3 -1
- data/spec/config/mongoid.yml +6 -0
- data/spec/decorator_spec.rb +3 -1
- data/spec/extensions/relations/adapters/active_record_spec.rb +26 -0
- data/spec/extensions/relations/adapters/adapters_module_spec.rb +11 -0
- data/spec/extensions/relations/adapters/mongoid_spec.rb +26 -0
- data/spec/extensions/relations/dsl_methods_spec.rb +159 -0
- data/spec/extensions/relations/mapper_spec.rb +88 -0
- data/spec/extensions/relations/validations/active_record_spec.rb +46 -0
- data/spec/extensions/relations/validations/mongoid_spec.rb +88 -0
- data/spec/nested_representer_spec.rb +4 -13
- data/spec/present_with_spec.rb +3 -12
- data/spec/relations_spec.rb +76 -0
- data/spec/representer_spec.rb +3 -12
- data/spec/spec_helper.rb +15 -1
- data/spec/support/{article.rb → all/article.rb} +3 -1
- data/spec/support/{article_representer.rb → all/article_representer.rb} +2 -0
- data/spec/support/all/grape_app_context.rb +18 -0
- data/spec/support/{order.rb → all/order.rb} +3 -1
- data/spec/support/{order_representer.rb → all/order_representer.rb} +3 -1
- data/spec/support/{product.rb → all/product.rb} +2 -0
- data/spec/support/{product_representer.rb → all/product_representer.rb} +3 -1
- data/spec/support/{user.rb → all/user.rb} +2 -0
- data/spec/support/{user_representer.rb → all/user_representer.rb} +2 -0
- data/spec/support/mongoid/relational_models/cart.rb +7 -0
- data/spec/support/mongoid/relational_models/item.rb +7 -0
- data/spec/support/mongoid/relational_models/mongoid_cart_representer.rb +27 -0
- data/spec/support/mongoid/relational_models/mongoid_item_representer.rb +13 -0
- metadata +55 -11
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Roar
|
5
|
+
module Extensions
|
6
|
+
module Relations
|
7
|
+
module Validations
|
8
|
+
module Misc
|
9
|
+
def invalid_relation(valid, invalid)
|
10
|
+
raise Exceptions::InvalidRelationError,
|
11
|
+
"Expected #{valid}, got #{invalid}!"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Roar
|
5
|
+
module Extensions
|
6
|
+
module Relations
|
7
|
+
module Validations
|
8
|
+
module Mongoid
|
9
|
+
include Validations::Misc
|
10
|
+
|
11
|
+
def belongs_to_valid?(relation)
|
12
|
+
relation = klass.reflect_on_association(relation)
|
13
|
+
|
14
|
+
return true if relation[:relation] ==
|
15
|
+
::Mongoid::Relations::Referenced::In
|
16
|
+
|
17
|
+
invalid_relation(
|
18
|
+
::Mongoid::Relations::Referenced::In, relation[:relation]
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def embeds_many_valid?(relation)
|
23
|
+
relation = klass.reflect_on_association(relation)
|
24
|
+
|
25
|
+
return true if relation[:relation] ==
|
26
|
+
::Mongoid::Relations::Embedded::Many
|
27
|
+
|
28
|
+
invalid_relation(
|
29
|
+
::Mongoid::Relations::Embedded::Many, relation[:relation]
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def embeds_one_valid?(relation)
|
34
|
+
relation = klass.reflect_on_association(relation)
|
35
|
+
|
36
|
+
return true if relation[:relation] ==
|
37
|
+
::Mongoid::Relations::Embedded::One
|
38
|
+
|
39
|
+
invalid_relation(
|
40
|
+
::Mongoid::Relations::Embedded::One, relation[:relation]
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
# rubocop:disable Style/PredicateName
|
45
|
+
def has_many_valid?(relation)
|
46
|
+
relation = klass.reflect_on_association(relation)
|
47
|
+
|
48
|
+
return true if relation[:relation] ==
|
49
|
+
::Mongoid::Relations::Referenced::Many
|
50
|
+
|
51
|
+
invalid_relation(
|
52
|
+
::Mongoid::Relations::Referenced::Many, relation[:relation]
|
53
|
+
)
|
54
|
+
end
|
55
|
+
# rubocop:enable Style/PredicateName
|
56
|
+
|
57
|
+
# rubocop:disable Style/PredicateName
|
58
|
+
def has_and_belongs_to_many_valid?(relation)
|
59
|
+
relation = klass.reflect_on_association(relation)
|
60
|
+
|
61
|
+
return true if relation[:relation] ==
|
62
|
+
::Mongoid::Relations::Referenced::ManyToMany
|
63
|
+
|
64
|
+
invalid_relation(
|
65
|
+
::Mongoid::Relations::Referenced::ManyToMany,
|
66
|
+
relation[:relation]
|
67
|
+
)
|
68
|
+
end
|
69
|
+
# rubocop:enable Style/PredicateName
|
70
|
+
|
71
|
+
# rubocop:disable Style/PredicateName
|
72
|
+
def has_one_valid?(relation)
|
73
|
+
relation = klass.reflect_on_association(relation)
|
74
|
+
|
75
|
+
return true if relation[:relation] ==
|
76
|
+
::Mongoid::Relations::Referenced::One
|
77
|
+
|
78
|
+
invalid_relation(
|
79
|
+
::Mongoid::Relations::Referenced::One, relation[:relation]
|
80
|
+
)
|
81
|
+
end
|
82
|
+
# rubocop:enable Style/PredicateName
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/grape/roar/formatter.rb
CHANGED
data/lib/grape/roar/version.rb
CHANGED
data/spec/decorator_spec.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
describe Grape::Roar::Extensions::Relations::Adapters::ActiveRecord, active_record: true do
|
3
|
+
let(:model) { Class.new(ActiveRecord::Base) }
|
4
|
+
subject { described_class.new(model) }
|
5
|
+
|
6
|
+
context '.valid_for?' do
|
7
|
+
it 'is only valid for ActiveRecord::Base descendants' do
|
8
|
+
expect(described_class.valid_for?(model)).to eql(true)
|
9
|
+
expect(described_class.valid_for?('foo')).to eql(false)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context '#collection_methods' do
|
14
|
+
it 'should return all collection methods' do
|
15
|
+
expect(subject.collection_methods)
|
16
|
+
.to match_array(%i[has_many has_and_belongs_to_many])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context '#single_entity_methods' do
|
21
|
+
it 'should return all single entity methods' do
|
22
|
+
expect(subject.single_entity_methods)
|
23
|
+
.to match_array(%i[has_one belongs_to])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
describe Grape::Roar::Extensions::Relations::Adapters, active_record: true do
|
3
|
+
context '.for' do
|
4
|
+
let(:model) { Class.new(ActiveRecord::Base) }
|
5
|
+
|
6
|
+
it 'looks up the correct adapter for a given class' do
|
7
|
+
expect(described_class.for(model))
|
8
|
+
.to be_an_instance_of(described_class::ActiveRecord)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
describe Grape::Roar::Extensions::Relations::Adapters::Mongoid, mongoid: true do
|
3
|
+
let(:model) { Class.new.tap { |c| c.include(::Mongoid::Document) } }
|
4
|
+
subject { described_class.new(model) }
|
5
|
+
|
6
|
+
context '.valid_for?' do
|
7
|
+
it 'is only valid for classes that mixed in Mongoid::Document' do
|
8
|
+
expect(described_class.valid_for?(model)).to eql(true)
|
9
|
+
expect(described_class.valid_for?('foo')).to eql(false)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context '#collection_methods' do
|
14
|
+
it 'should return all collection methods' do
|
15
|
+
expect(subject.collection_methods)
|
16
|
+
.to match_array(%i[embeds_many has_many has_and_belongs_to_many])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context '#single_entity_methods' do
|
21
|
+
it 'should return all single entity methods' do
|
22
|
+
expect(subject.single_entity_methods)
|
23
|
+
.to match_array(%i[has_one belongs_to embeds_one])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Grape::Roar::Extensions::Relations::DSLMethods do
|
4
|
+
subject do
|
5
|
+
Class.new.tap { |c| c.singleton_class.include(described_class) }
|
6
|
+
end
|
7
|
+
|
8
|
+
context '#link_relation' do
|
9
|
+
let(:relation) { double }
|
10
|
+
let(:collection) { false }
|
11
|
+
|
12
|
+
before do
|
13
|
+
allow(subject).to receive(:link)
|
14
|
+
allow(subject).to receive(:links)
|
15
|
+
end
|
16
|
+
|
17
|
+
after { subject.link_relation(relation, collection) }
|
18
|
+
|
19
|
+
it 'calls the correct method' do
|
20
|
+
expect(subject).to receive(:link).with(relation)
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'with a collection of objects' do
|
24
|
+
let(:collection) { true }
|
25
|
+
|
26
|
+
it 'uses the links method' do
|
27
|
+
expect(subject).to receive(:links).with(relation)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context '#name_for_represented' do
|
33
|
+
let(:represented) { double }
|
34
|
+
|
35
|
+
after { subject.name_for_represented(represented) }
|
36
|
+
|
37
|
+
it 'calls the methods correctly' do
|
38
|
+
expect(subject).to receive_message_chain(
|
39
|
+
:relational_mapper, :adapter, :name_for_represented
|
40
|
+
).with(represented)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'with relational mapper' do
|
45
|
+
let(:relational_mapper) { double }
|
46
|
+
|
47
|
+
before do
|
48
|
+
allow(subject).to receive(:relational_mapper)
|
49
|
+
.and_return(relational_mapper)
|
50
|
+
end
|
51
|
+
|
52
|
+
context '#relation' do
|
53
|
+
let(:relation_name) { double }
|
54
|
+
let(:relation_kind) { :has_many }
|
55
|
+
let(:opts) { {} }
|
56
|
+
|
57
|
+
after { subject.relation(relation_kind, relation_name, opts) }
|
58
|
+
|
59
|
+
it 'correctly stores the info in mapper' do
|
60
|
+
expect(relational_mapper).to receive(:[]=).with(
|
61
|
+
relation_name, opts.merge(relation_kind: relation_kind)
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context '#link_self' do
|
67
|
+
after { subject.link_self }
|
68
|
+
|
69
|
+
it 'calls the method correctly' do
|
70
|
+
expect(relational_mapper).to receive(:[]=).with(
|
71
|
+
:self, relation_kind: :self
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context '#map_base_url' do
|
78
|
+
let(:grape_request) do
|
79
|
+
OpenStruct.new(base_url: 'foo/', script_name: 'v1')
|
80
|
+
end
|
81
|
+
|
82
|
+
let(:opts) { { env: double } }
|
83
|
+
|
84
|
+
before do
|
85
|
+
allow(Grape::Request).to receive(:new).with(opts[:env])
|
86
|
+
.and_return(grape_request)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'provides a default implementation' do
|
90
|
+
expect(subject.map_base_url.call(opts)).to eql('foo/v1')
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'with user provided block' do
|
94
|
+
let(:block) { proc {} }
|
95
|
+
|
96
|
+
it 'should return the user block' do
|
97
|
+
subject.map_base_url(&block)
|
98
|
+
expect(subject.map_base_url).to eql(block)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context '#map_self_url' do
|
104
|
+
after { subject.map_self_url }
|
105
|
+
|
106
|
+
it 'calls the correct method' do
|
107
|
+
expect(subject).to receive(:link).with(:self)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context '#map_resource_path' do
|
112
|
+
let(:object) { OpenStruct.new(id: 4) }
|
113
|
+
let(:opts) { double }
|
114
|
+
let(:relation) { 'baz' }
|
115
|
+
|
116
|
+
it 'provides a default implementation' do
|
117
|
+
expect(
|
118
|
+
subject.map_resource_path.call(opts, object, relation)
|
119
|
+
).to eql('baz/4')
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'with user provided block' do
|
123
|
+
let(:block) { proc {} }
|
124
|
+
|
125
|
+
it 'should return the user block' do
|
126
|
+
subject.map_resource_path(&block)
|
127
|
+
expect(subject.map_resource_path).to eql(block)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context '#represent' do
|
133
|
+
let(:object) { double }
|
134
|
+
let(:options) { double }
|
135
|
+
|
136
|
+
let(:relations_mapped) { false }
|
137
|
+
|
138
|
+
before do
|
139
|
+
expect(subject).to receive(:relations_mapped)
|
140
|
+
.and_return(relations_mapped)
|
141
|
+
end
|
142
|
+
|
143
|
+
after { subject.represent(object, options) }
|
144
|
+
|
145
|
+
it 'should map relations and invoke super' do
|
146
|
+
expect(subject).to receive(:map_relations).with(object)
|
147
|
+
expect(subject.superclass).to receive(:represent).with(object, options)
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'with relations mapped' do
|
151
|
+
let(:relations_mapped) { true }
|
152
|
+
|
153
|
+
it 'should not map relations and invoke super' do
|
154
|
+
expect(subject).to_not receive(:map_relations).with(object)
|
155
|
+
expect(subject.superclass).to receive(:represent).with(object, options)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Grape::Roar::Extensions::Relations::Mapper do
|
4
|
+
let(:entity) { double }
|
5
|
+
let(:klass) { double }
|
6
|
+
|
7
|
+
subject { described_class.new(entity) }
|
8
|
+
|
9
|
+
context '#initialize' do
|
10
|
+
it 'assigns the correct variables' do
|
11
|
+
expect(subject.instance_variable_get(:@entity)).to eql(entity)
|
12
|
+
expect(subject.instance_variable_get(:@config)).to eql({})
|
13
|
+
subject
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context '#adapter' do
|
18
|
+
let(:klass) { class_double('ActiveRecord::Base') }
|
19
|
+
|
20
|
+
before do
|
21
|
+
subject.instance_variable_set(:@model_klass, klass)
|
22
|
+
end
|
23
|
+
|
24
|
+
after { subject.adapter }
|
25
|
+
|
26
|
+
it 'should call the correct method' do
|
27
|
+
expect(Grape::Roar::Extensions::Relations::Adapters)
|
28
|
+
.to receive(:for).with(klass)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context '#decorate' do
|
33
|
+
let(:adapter) { double }
|
34
|
+
let(:config) do
|
35
|
+
{ test_single: {
|
36
|
+
relation_kind: :belongs_to, embedded: true, misc_opt: 'foo'
|
37
|
+
},
|
38
|
+
test_collection: {
|
39
|
+
relation_kind: :has_many, embedded: true, misc_opt: 'baz'
|
40
|
+
} }
|
41
|
+
end
|
42
|
+
|
43
|
+
let(:klass) { double }
|
44
|
+
|
45
|
+
before do
|
46
|
+
allow(subject).to receive(:adapter).and_return(adapter)
|
47
|
+
|
48
|
+
allow(adapter).to receive(:collection_methods).and_return(%i[has_many])
|
49
|
+
allow(adapter).to receive(:single_entity_methods).and_return(%i[belongs_to])
|
50
|
+
allow(entity).to receive(:name).and_return('Foo::Bar')
|
51
|
+
|
52
|
+
subject.instance_variable_set(:@config, config)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should correctly decorate the entity' do
|
56
|
+
expect(adapter).to receive(:belongs_to_valid?).with(
|
57
|
+
'test_single'
|
58
|
+
).and_return(true)
|
59
|
+
|
60
|
+
expect(adapter).to receive(:has_many_valid?).with(
|
61
|
+
'test_collection'
|
62
|
+
).and_return(true)
|
63
|
+
|
64
|
+
expect(entity).to receive(:collection).with(
|
65
|
+
:test_collection, config[:test_collection]
|
66
|
+
)
|
67
|
+
|
68
|
+
expect(entity).to receive(:property).with(
|
69
|
+
:test_single, config[:test_single]
|
70
|
+
)
|
71
|
+
|
72
|
+
subject.decorate(klass)
|
73
|
+
expect(subject.instance_variable_get(:@model_klass)).to eql(klass)
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'with an invalid relation type' do
|
77
|
+
let(:config) do
|
78
|
+
{ test_single: { relation_kind: :has_baz, misc_opt: 'foo' } }
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'will raise the correct exception' do
|
82
|
+
expect { subject.decorate(klass) }.to raise_error(
|
83
|
+
Grape::Roar::Extensions::Relations::Exceptions::UnsupportedRelationError
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|