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,46 @@
1
+ # frozen_string_literal: true
2
+ describe Grape::Roar::Extensions::Relations::Validations::ActiveRecord, active_record: true do
3
+ let(:model_klass) { double }
4
+
5
+ subject do
6
+ klass = Class.new do
7
+ def initialize(klass)
8
+ @klass = klass
9
+ end
10
+
11
+ attr_reader :klass
12
+ end
13
+
14
+ klass.include(described_class)
15
+ klass.new(model_klass)
16
+ end
17
+
18
+ before do
19
+ expect(model_klass).to receive(:reflections).twice
20
+ .and_return(reflections)
21
+ end
22
+
23
+ %w[
24
+ belongs_to
25
+ has_many
26
+ has_one
27
+ has_and_belongs_to_many
28
+ ].each do |relation|
29
+ context "##{relation}_valid?" do
30
+ let(:relation_klass) do
31
+ "::ActiveRecord::Reflection::#{relation.camelize}"\
32
+ 'Reflection'.constantize.allocate
33
+ end
34
+
35
+ let(:reflections) { { test: relation_klass, fail: Class.new } }
36
+
37
+ it 'properly validates the relation' do
38
+ expect(subject.send("#{relation}_valid?", :test)).to eql(true)
39
+ expect { subject.send("#{relation}_valid?", :fail) }.to raise_error(
40
+ Grape::Roar::Extensions::Relations::\
41
+ Exceptions::InvalidRelationError
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+ describe Grape::Roar::Extensions::Relations::Validations::Mongoid, mongoid: true do
3
+ let(:model_klass) { double }
4
+
5
+ subject do
6
+ klass = Class.new do
7
+ def initialize(klass)
8
+ @klass = klass
9
+ end
10
+
11
+ attr_reader :klass
12
+ end
13
+
14
+ klass.include(described_class)
15
+ klass.new(model_klass)
16
+ end
17
+
18
+ let(:map_reflection) do
19
+ proc do |r|
20
+ case r
21
+ when /and/ then 'ManyToMany'
22
+ when /many/ then 'Many'
23
+ when /one/ then 'One'
24
+ when /belongs/ then 'In'
25
+ end
26
+ end
27
+ end
28
+
29
+ before do
30
+ expect(model_klass).to receive(:reflect_on_association).twice do |r|
31
+ if r == :test_rel
32
+ {
33
+ relation: "Mongoid::Relations::#{relation_klass}"\
34
+ "::#{map_reflection.call(test_method)}".constantize
35
+ }
36
+ else
37
+ { relation: double }
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'referenced' do
43
+ let(:relation_klass) { 'Referenced' }
44
+
45
+ %i[
46
+ belongs_to
47
+ has_and_belongs_to_many
48
+ has_many
49
+ has_one
50
+ ].each do |test_method|
51
+ context "##{test_method}_valid? validates the relation" do
52
+ let(:test_method) { test_method }
53
+
54
+ it 'properly validates the relation' do
55
+ expect(subject.send("#{test_method}_valid?", :test_rel)).to eql(true)
56
+
57
+ expect do
58
+ subject.send("#{test_method}_valid?", :fail)
59
+ end.to raise_error(
60
+ Grape::Roar::Extensions::Relations::\
61
+ Exceptions::InvalidRelationError
62
+ )
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ context 'referenced' do
69
+ let(:relation_klass) { 'Embedded' }
70
+
71
+ %i[embeds_one embeds_many].each do |test_method|
72
+ context "##{test_method}_valid? validates the relation" do
73
+ let(:test_method) { test_method }
74
+
75
+ it 'properly validates the relation' do
76
+ expect(subject.send("#{test_method}_valid?", :test_rel)).to eql(true)
77
+
78
+ expect do
79
+ subject.send("#{test_method}_valid?", :fail)
80
+ end.to raise_error(
81
+ Grape::Roar::Extensions::Relations::\
82
+ Exceptions::InvalidRelationError
83
+ )
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -1,20 +1,11 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe Grape::Roar do
4
- subject do
5
- Class.new(Grape::API)
6
- end
7
3
 
8
- before do
9
- subject.format :json
10
- subject.formatter :json, Grape::Formatter::Roar
11
- end
12
-
13
- def app
14
- subject
15
- end
16
4
 
5
+ describe Grape::Roar do
17
6
  context 'nested representer' do
7
+ include_context 'Grape API App'
8
+
18
9
  before do
19
10
  subject.get('/order/:id') do
20
11
  order = Order.new(id: params[:id], client_id: 42)
@@ -1,18 +1,9 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe Grape::Roar do
4
- subject do
5
- Class.new(Grape::API)
6
- end
7
3
 
8
- before do
9
- subject.format :json
10
- subject.formatter :json, Grape::Formatter::Roar
11
- end
12
4
 
13
- def app
14
- subject
15
- end
5
+ describe Grape::Roar do
6
+ include_context 'Grape API App'
16
7
 
17
8
  context 'using present' do
18
9
  before do
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Grape::Roar::Extensions::Relations do
4
+ context '.included' do
5
+ subject { Class.new }
6
+ after { subject.include(described_class) }
7
+
8
+ it 'mixes DSLMethods into the singleton class of its target' do
9
+ expect(subject.singleton_class).to receive(:include).with(
10
+ Grape::Roar::Extensions::Relations::DSLMethods
11
+ )
12
+ end
13
+ end
14
+
15
+ context 'with mongoid', mongoid: true do
16
+ include_context 'Grape API App'
17
+
18
+ # Make sure Mongo is empty
19
+ before(:each) { Mongoid::Config.purge! }
20
+
21
+ let(:result) { JSON.parse(last_response.body) }
22
+
23
+ context 'has_many, embedded: false' do
24
+ before do
25
+ subject.get('/carts/:id') do
26
+ cart = Cart.create(id: params[:id])
27
+ Array.new(5).map { Item.create(cart: cart) }
28
+ present(cart, with: MongoidCartRepresenter)
29
+ end
30
+
31
+ get '/carts/1'
32
+ end
33
+
34
+ it 'correctly generates the self link' do
35
+ expect(result['_links']['self']['href']).to eql(
36
+ 'http://example.org/carts/1'
37
+ )
38
+ end
39
+
40
+ it 'correctly generates links for items' do
41
+ expect(result['_links']['items'].map { |l| l['href'] }).to all(
42
+ match(/^http:\/\/example\.org\/items\/[A-Za-z0-9]*$/)
43
+ )
44
+ end
45
+ end
46
+
47
+ context 'belongs_to, embedded: true' do
48
+ before do
49
+ subject.get('/items/:id') do
50
+ present(
51
+ Item.create(id: params[:id], cart: Cart.create),
52
+ with: MongoidItemRepresenter
53
+ )
54
+ end
55
+
56
+ get '/items/1'
57
+ end
58
+
59
+ it 'correctly generates the self link' do
60
+ expect(result['_links']['self']['href']).to eql(
61
+ 'http://example.org/items/1'
62
+ )
63
+ end
64
+
65
+ it 'correctly represents the embedded cart' do
66
+ expect(result['_embedded']['cart']['_links']['self']['href'])
67
+ .to match(/^http:\/\/example\.org\/carts\/[A-Za-z0-9]*$/)
68
+
69
+ expect(result['_embedded']['cart']['_links']['items'].count).to eql(1)
70
+ expect(result['_embedded']['cart']['_links']['items'].first).to eql(
71
+ result['_links']['self']
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,18 +1,9 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe Grape::Roar do
4
- subject do
5
- Class.new(Grape::API)
6
- end
7
3
 
8
- before do
9
- subject.format :json
10
- subject.formatter :json, Grape::Formatter::Roar
11
- end
12
4
 
13
- def app
14
- subject
15
- end
5
+ describe Grape::Roar do
6
+ include_context 'Grape API App'
16
7
 
17
8
  context 'representer' do
18
9
  before do
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
4
  $LOAD_PATH.unshift(File.dirname(__FILE__))
3
5
 
4
6
  require 'bundler'
5
7
  Bundler.setup :default, :test
8
+ Bundler.require
6
9
 
7
10
  require 'grape'
8
11
 
@@ -12,10 +15,21 @@ require 'roar/hypermedia'
12
15
 
13
16
  require 'grape/roar'
14
17
  require 'rack/test'
18
+
15
19
  require 'rspec'
16
20
 
17
21
  RSpec.configure do |config|
18
22
  config.include Rack::Test::Methods
23
+ config.filter_run_excluding(
24
+ active_record: true, mongoid: true
25
+ )
19
26
  end
20
27
 
21
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
28
+ Dir["#{File.dirname(__FILE__)}/support/all/**/*.rb"].each { |f| require f }
29
+
30
+ # For Relational Extension Tests
31
+ if defined?(Mongoid)
32
+ ENV['MONGOID_ENV'] ||= 'test'
33
+ Mongoid.load!('./spec/config/mongoid.yml')
34
+ Dir["#{File.dirname(__FILE__)}/support/mongoid/**/*.rb"].each { |f| require f }
35
+ end
@@ -1,4 +1,6 @@
1
- require 'support/article_representer'
1
+ # frozen_string_literal: true
2
+
3
+ require 'support/all/article_representer'
2
4
 
3
5
  class Article
4
6
  include Roar::JSON
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ArticleRepresenter
2
4
  include Roar::JSON
3
5
  include Roar::Hypermedia
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+
5
+ shared_context 'Grape API App' do
6
+ subject do
7
+ Class.new(Grape::API)
8
+ end
9
+
10
+ before do
11
+ subject.format :json
12
+ subject.formatter :json, Grape::Formatter::Roar
13
+ end
14
+
15
+ def app
16
+ subject
17
+ end
18
+ end
@@ -1,4 +1,6 @@
1
- require 'support/order_representer'
1
+ # frozen_string_literal: true
2
+
3
+ require 'support/all/order_representer'
2
4
 
3
5
  class Order
4
6
  include Roar::JSON
@@ -1,4 +1,6 @@
1
- require 'support/article'
1
+ # frozen_string_literal: true
2
+
3
+ require 'support/all/article'
2
4
 
3
5
  module OrderRepresenter
4
6
  include Roar::JSON
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Product
2
4
  attr_accessor :title, :id
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'roar/json/hal'
2
4
 
3
5
  module ProductRepresenter
@@ -10,6 +12,6 @@ module ProductRepresenter
10
12
 
11
13
  link :self do |opts|
12
14
  request = Grape::Request.new(opts[:env])
13
- "#{request.url}"
15
+ request.url.to_s
14
16
  end
15
17
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class User
2
4
  attr_accessor :id, :name
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class UserRepresenter < Grape::Roar::Decorator
2
4
  include Roar::JSON
3
5
  include Roar::Hypermedia
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Cart
4
+ include ::Mongoid::Document
5
+
6
+ has_many :items
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Item
4
+ include ::Mongoid::Document
5
+
6
+ belongs_to :cart
7
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MongoidCartRepresenter
4
+ include Roar::JSON
5
+ include Roar::JSON::HAL
6
+ include Roar::Hypermedia
7
+
8
+ include Grape::Roar::Extensions::Relations
9
+
10
+ relation :has_many, :items
11
+
12
+ link_self
13
+ end
14
+
15
+ # Class style below, but want to test module
16
+
17
+ # class MongoidCartRepresenter < Grape::Roar::Decorator
18
+ # include Roar::JSON
19
+ # include Roar::JSON::HAL
20
+ # include Roar::Hypermedia
21
+
22
+ # include Grape::Roar::Extensions::Relations
23
+
24
+ # relation :has_many, :items
25
+
26
+ # link_self
27
+ # end