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