grape-roar 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +6 -2
  5. data/.rubocop_todo.yml +38 -9
  6. data/.travis.yml +29 -3
  7. data/Appraisals +9 -0
  8. data/CHANGELOG.md +10 -0
  9. data/Gemfile +6 -3
  10. data/README.md +218 -0
  11. data/Rakefile +3 -1
  12. data/gemfiles/with_activerecord.gemfile +22 -0
  13. data/gemfiles/with_mongoid.gemfile +22 -0
  14. data/grape-roar.gemspec +3 -0
  15. data/lib/grape-roar.rb +2 -0
  16. data/lib/grape/roar.rb +3 -0
  17. data/lib/grape/roar/decorator.rb +2 -0
  18. data/lib/grape/roar/extensions.rb +3 -0
  19. data/lib/grape/roar/extensions/relations.rb +23 -0
  20. data/lib/grape/roar/extensions/relations/adapters.rb +22 -0
  21. data/lib/grape/roar/extensions/relations/adapters/active_record.rb +35 -0
  22. data/lib/grape/roar/extensions/relations/adapters/base.rb +49 -0
  23. data/lib/grape/roar/extensions/relations/adapters/mongoid.rb +38 -0
  24. data/lib/grape/roar/extensions/relations/dsl_methods.rb +86 -0
  25. data/lib/grape/roar/extensions/relations/exceptions.rb +14 -0
  26. data/lib/grape/roar/extensions/relations/mapper.rb +89 -0
  27. data/lib/grape/roar/extensions/relations/validations.rb +5 -0
  28. data/lib/grape/roar/extensions/relations/validations/active_record.rb +67 -0
  29. data/lib/grape/roar/extensions/relations/validations/misc.rb +18 -0
  30. data/lib/grape/roar/extensions/relations/validations/mongoid.rb +88 -0
  31. data/lib/grape/roar/formatter.rb +2 -0
  32. data/lib/grape/roar/representer.rb +2 -0
  33. data/lib/grape/roar/version.rb +3 -1
  34. data/spec/config/mongoid.yml +6 -0
  35. data/spec/decorator_spec.rb +3 -1
  36. data/spec/extensions/relations/adapters/active_record_spec.rb +26 -0
  37. data/spec/extensions/relations/adapters/adapters_module_spec.rb +11 -0
  38. data/spec/extensions/relations/adapters/mongoid_spec.rb +26 -0
  39. data/spec/extensions/relations/dsl_methods_spec.rb +159 -0
  40. data/spec/extensions/relations/mapper_spec.rb +88 -0
  41. data/spec/extensions/relations/validations/active_record_spec.rb +46 -0
  42. data/spec/extensions/relations/validations/mongoid_spec.rb +88 -0
  43. data/spec/nested_representer_spec.rb +4 -13
  44. data/spec/present_with_spec.rb +3 -12
  45. data/spec/relations_spec.rb +76 -0
  46. data/spec/representer_spec.rb +3 -12
  47. data/spec/spec_helper.rb +15 -1
  48. data/spec/support/{article.rb → all/article.rb} +3 -1
  49. data/spec/support/{article_representer.rb → all/article_representer.rb} +2 -0
  50. data/spec/support/all/grape_app_context.rb +18 -0
  51. data/spec/support/{order.rb → all/order.rb} +3 -1
  52. data/spec/support/{order_representer.rb → all/order_representer.rb} +3 -1
  53. data/spec/support/{product.rb → all/product.rb} +2 -0
  54. data/spec/support/{product_representer.rb → all/product_representer.rb} +3 -1
  55. data/spec/support/{user.rb → all/user.rb} +2 -0
  56. data/spec/support/{user_representer.rb → all/user_representer.rb} +2 -0
  57. data/spec/support/mongoid/relational_models/cart.rb +7 -0
  58. data/spec/support/mongoid/relational_models/item.rb +7 -0
  59. data/spec/support/mongoid/relational_models/mongoid_cart_representer.rb +27 -0
  60. data/spec/support/mongoid/relational_models/mongoid_item_representer.rb +13 -0
  61. 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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Formatter
3
5
  module Roar
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Roar
3
5
  module Representer
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Roar
3
- VERSION = '0.4.0'
5
+ VERSION = '0.4.1'
4
6
  end
5
7
  end
@@ -0,0 +1,6 @@
1
+ test:
2
+ clients:
3
+ default:
4
+ database: grape_roar_test
5
+ hosts:
6
+ - localhost:27017
@@ -1,4 +1,6 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+
2
4
 
3
5
  describe Grape::Roar::Decorator do
4
6
  subject do
@@ -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