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,22 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "mongoid", ">= 5.0.0", require: "mongoid"
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem "appraisal", "2.2.0"
|
9
|
+
end
|
10
|
+
|
11
|
+
group :test do
|
12
|
+
gem "rack-test"
|
13
|
+
gem "rspec", "~> 3.1"
|
14
|
+
end
|
15
|
+
|
16
|
+
group :development, :test do
|
17
|
+
gem "nokogiri", "1.6.3.1"
|
18
|
+
gem "rake", "~> 10.5.0"
|
19
|
+
gem "rubocop", "0.49.1"
|
20
|
+
end
|
21
|
+
|
22
|
+
gemspec path: "../"
|
data/grape-roar.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
require File.expand_path('../lib/grape/roar/version', __FILE__)
|
3
5
|
|
4
6
|
Gem::Specification.new do |gem|
|
@@ -17,6 +19,7 @@ Gem::Specification.new do |gem|
|
|
17
19
|
gem.version = Grape::Roar::VERSION
|
18
20
|
|
19
21
|
gem.add_dependency 'grape'
|
22
|
+
gem.add_dependency 'multi_json'
|
20
23
|
gem.add_dependency 'roar', '~> 1.1.0'
|
21
24
|
gem.required_ruby_version = '>= 2.1.0'
|
22
25
|
end
|
data/lib/grape-roar.rb
CHANGED
data/lib/grape/roar.rb
CHANGED
data/lib/grape/roar/decorator.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'grape/roar/extensions/relations/validations'
|
4
|
+
require 'grape/roar/extensions/relations/adapters'
|
5
|
+
require 'grape/roar/extensions/relations/dsl_methods'
|
6
|
+
require 'grape/roar/extensions/relations/exceptions'
|
7
|
+
require 'grape/roar/extensions/relations/mapper'
|
8
|
+
|
9
|
+
module Grape
|
10
|
+
module Roar
|
11
|
+
module Extensions
|
12
|
+
module Relations
|
13
|
+
class << self
|
14
|
+
def included(other)
|
15
|
+
class << other
|
16
|
+
include Extensions::Relations::DSLMethods
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'grape/roar/extensions/relations/adapters/base'
|
4
|
+
require 'grape/roar/extensions/relations/adapters/active_record'
|
5
|
+
require 'grape/roar/extensions/relations/adapters/mongoid'
|
6
|
+
|
7
|
+
module Grape
|
8
|
+
module Roar
|
9
|
+
module Extensions
|
10
|
+
module Relations
|
11
|
+
module Adapters
|
12
|
+
def self.for(klass)
|
13
|
+
(constants - [:Base]).inject(nil) do |m, c|
|
14
|
+
obj = const_get(c)
|
15
|
+
obj.valid_for?(klass) ? obj.new(klass) : m
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Roar
|
5
|
+
module Extensions
|
6
|
+
module Relations
|
7
|
+
module Adapters
|
8
|
+
class ActiveRecord < Base
|
9
|
+
include Validations::ActiveRecord
|
10
|
+
|
11
|
+
valid_for { |klass| klass < ::ActiveRecord::Base }
|
12
|
+
|
13
|
+
def collection_methods
|
14
|
+
@collection_methods ||= %i[has_many has_and_belongs_to_many]
|
15
|
+
end
|
16
|
+
|
17
|
+
def name_for_represented(represented)
|
18
|
+
klass_name = case represented
|
19
|
+
when ::ActiveRecord::Relation
|
20
|
+
represented.klass.name
|
21
|
+
else
|
22
|
+
represented.class.name
|
23
|
+
end
|
24
|
+
klass_name.demodulize.pluralize.downcase
|
25
|
+
end
|
26
|
+
|
27
|
+
def single_entity_methods
|
28
|
+
@single_entity_methods ||= %i[has_one belongs_to]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Roar
|
5
|
+
module Extensions
|
6
|
+
module Relations
|
7
|
+
module Adapters
|
8
|
+
class Base
|
9
|
+
class << self
|
10
|
+
def valid_for?(klass)
|
11
|
+
valid_proc.call(klass)
|
12
|
+
rescue
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid_for(&block)
|
17
|
+
@valid_proc = block
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :valid_proc
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(klass)
|
26
|
+
@klass = klass
|
27
|
+
end
|
28
|
+
|
29
|
+
def collection_methods
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
def name_for_represented(_represented)
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
def single_entity_methods
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :klass
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Roar
|
5
|
+
module Extensions
|
6
|
+
module Relations
|
7
|
+
module Adapters
|
8
|
+
class Mongoid < Base
|
9
|
+
include Validations::Mongoid
|
10
|
+
|
11
|
+
valid_for { |klass| klass < ::Mongoid::Document }
|
12
|
+
|
13
|
+
def collection_methods
|
14
|
+
@collection_methods ||= %i[
|
15
|
+
embeds_many has_many has_and_belongs_to_many
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
def name_for_represented(represented)
|
20
|
+
klass_name = if represented.instance_of?(
|
21
|
+
::Mongoid::Relations::Targets::Enumerable
|
22
|
+
)
|
23
|
+
represented.klass.name
|
24
|
+
else
|
25
|
+
represented.class.name
|
26
|
+
end
|
27
|
+
klass_name.demodulize.pluralize.downcase
|
28
|
+
end
|
29
|
+
|
30
|
+
def single_entity_methods
|
31
|
+
@single_entity_methods ||= %i[has_one belongs_to embeds_one]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Roar
|
5
|
+
module Extensions
|
6
|
+
module Relations
|
7
|
+
module DSLMethods
|
8
|
+
def link_relation(relation, is_collection = false, dsl = self)
|
9
|
+
send(is_collection ? :links : :link, relation) do |opts|
|
10
|
+
data = represented.send(relation)
|
11
|
+
|
12
|
+
mapped = Array.wrap(data).map do |object|
|
13
|
+
href = [dsl.map_base_url.call(opts),
|
14
|
+
dsl.map_resource_path.call(opts, object, relation)].join('/')
|
15
|
+
|
16
|
+
is_collection ? { href: href } : href
|
17
|
+
end
|
18
|
+
|
19
|
+
is_collection ? mapped : mapped.first
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def link_self
|
24
|
+
relational_mapper[:self] = { relation_kind: :self }
|
25
|
+
end
|
26
|
+
|
27
|
+
def map_base_url(&block)
|
28
|
+
@map_base_url ||= if block.nil?
|
29
|
+
proc do |opts|
|
30
|
+
request = Grape::Request.new(opts[:env])
|
31
|
+
"#{request.base_url}#{request.script_name}"
|
32
|
+
end
|
33
|
+
else
|
34
|
+
block
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def map_self_url(dsl = self)
|
39
|
+
link :self do |opts|
|
40
|
+
resource_path = dsl.name_for_represented(represented)
|
41
|
+
[dsl.map_base_url.call(opts),
|
42
|
+
"#{resource_path}/#{represented.try(:id)}"].join('/')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def map_resource_path(&block)
|
47
|
+
@map_resource_path ||= if block.nil?
|
48
|
+
proc do |_opts, object, relation_name|
|
49
|
+
"#{relation_name}/#{object.id}"
|
50
|
+
end
|
51
|
+
else
|
52
|
+
block
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def name_for_represented(represented)
|
57
|
+
relational_mapper.adapter.name_for_represented(represented)
|
58
|
+
end
|
59
|
+
|
60
|
+
def relation(relation_kind, rname, opts = {})
|
61
|
+
relational_mapper[rname] = opts.merge(relation_kind: relation_kind)
|
62
|
+
end
|
63
|
+
|
64
|
+
def represent(object, _options)
|
65
|
+
object.extend(self) unless is_a?(Class)
|
66
|
+
map_relations(object) unless relations_mapped
|
67
|
+
is_a?(Class) ? super : object
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
attr_reader :relations_mapped
|
73
|
+
|
74
|
+
def map_relations(object)
|
75
|
+
relational_mapper.decorate(object.class)
|
76
|
+
@relations_mapped = true
|
77
|
+
end
|
78
|
+
|
79
|
+
def relational_mapper
|
80
|
+
@relational_mapper ||= Mapper.new(self)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Roar
|
5
|
+
module Extensions
|
6
|
+
module Relations
|
7
|
+
module Exceptions
|
8
|
+
class InvalidRelationError < StandardError; end
|
9
|
+
class UnsupportedRelationError < StandardError; end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Roar
|
5
|
+
module Extensions
|
6
|
+
module Relations
|
7
|
+
class Mapper
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def initialize(entity)
|
11
|
+
@entity = entity
|
12
|
+
@config = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def adapter
|
16
|
+
@adapter ||= Adapters.for(model_klass)
|
17
|
+
end
|
18
|
+
|
19
|
+
def decorate(klass)
|
20
|
+
@model_klass = klass
|
21
|
+
|
22
|
+
config.each_pair do |relation, opts|
|
23
|
+
representer_for(relation.to_s, opts) unless opts.key(:extend)
|
24
|
+
map_relation(relation, opts)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def_delegators :@config, :[], :[]=
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :config, :entity, :model_klass
|
33
|
+
|
34
|
+
def find_representer(base, target_name, const)
|
35
|
+
const = base.const_get(const)
|
36
|
+
return false if const.nil? || !const.is_a?(Module)
|
37
|
+
(const < ::Roar::JSON::HAL) && const.name
|
38
|
+
.downcase
|
39
|
+
.include?(target_name.singularize)
|
40
|
+
end
|
41
|
+
|
42
|
+
def map_collection(relation, opts)
|
43
|
+
return entity.link_relation(relation, true) unless opts.fetch(:embedded, false)
|
44
|
+
entity.collection(relation, opts)
|
45
|
+
end
|
46
|
+
|
47
|
+
def map_relation(relation, opts)
|
48
|
+
map = if adapter.collection_methods.include?(opts[:relation_kind])
|
49
|
+
:map_collection
|
50
|
+
elsif adapter.single_entity_methods
|
51
|
+
.include?(opts[:relation_kind]) || opts[:relation_kind] == :self
|
52
|
+
:map_single_entity
|
53
|
+
else raise Exceptions::UnsupportedRelationError,
|
54
|
+
'No such relation supported'
|
55
|
+
end
|
56
|
+
|
57
|
+
send(map, relation, opts)
|
58
|
+
validate_relation(relation, opts[:relation_kind])
|
59
|
+
end
|
60
|
+
|
61
|
+
def map_single_entity(relation, opts)
|
62
|
+
return entity.map_self_url if opts[:relation_kind] == :self
|
63
|
+
return entity.link_relation(relation) unless opts.fetch(:embedded, false)
|
64
|
+
entity.property(relation, opts)
|
65
|
+
end
|
66
|
+
|
67
|
+
def representer_for(relation, opts)
|
68
|
+
base_path = entity.name.deconstantize
|
69
|
+
base_path = base_path.empty? ? Object : base_path.safe_constantize
|
70
|
+
return if base_path.nil?
|
71
|
+
|
72
|
+
to_extend = base_path.constants
|
73
|
+
.find(&method(:find_representer).curry[
|
74
|
+
base_path, relation
|
75
|
+
])
|
76
|
+
|
77
|
+
opts.merge!(extend: "#{base_path}::#{to_extend}".safe_constantize)
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_relation(relation, kind)
|
81
|
+
validator_method = "#{kind}_valid?"
|
82
|
+
return true unless adapter.respond_to?(validator_method)
|
83
|
+
adapter.send(validator_method, relation.to_s)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Roar
|
5
|
+
module Extensions
|
6
|
+
module Relations
|
7
|
+
module Validations
|
8
|
+
module ActiveRecord
|
9
|
+
include Validations::Misc
|
10
|
+
|
11
|
+
def belongs_to_valid?(relation)
|
12
|
+
relation = klass.reflections[relation]
|
13
|
+
|
14
|
+
return true if relation.is_a?(
|
15
|
+
::ActiveRecord::Reflection::BelongsToReflection
|
16
|
+
)
|
17
|
+
|
18
|
+
invalid_relation(
|
19
|
+
::ActiveRecord::Reflection::BelongsToReflection,
|
20
|
+
relation.class
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_many_valid?(relation)
|
25
|
+
relation = klass.reflections[relation]
|
26
|
+
|
27
|
+
return true if relation.is_a?(
|
28
|
+
::ActiveRecord::Reflection::HasManyReflection
|
29
|
+
)
|
30
|
+
|
31
|
+
invalid_relation(
|
32
|
+
::ActiveRecord::Reflection::HasManyReflection,
|
33
|
+
relation.class
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_and_belongs_to_many_valid?(relation)
|
38
|
+
relation = klass.reflections[relation]
|
39
|
+
|
40
|
+
return true if relation.is_a?(
|
41
|
+
::ActiveRecord::Reflection::HasAndBelongsToManyReflection
|
42
|
+
)
|
43
|
+
|
44
|
+
invalid_relation(
|
45
|
+
::ActiveRecord::Reflection::HasAndBelongsToManyReflection,
|
46
|
+
relation.class
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_one_valid?(relation)
|
51
|
+
relation = klass.reflections[relation]
|
52
|
+
|
53
|
+
return true if relation.is_a?(
|
54
|
+
::ActiveRecord::Reflection::HasOneReflection
|
55
|
+
)
|
56
|
+
|
57
|
+
invalid_relation(
|
58
|
+
::ActiveRecord::Reflection::HasOneReflection,
|
59
|
+
relation.class
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|