graphiti-rb 1.0.alpha.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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +20 -0
- data/.yardopts +2 -0
- data/Appraisals +11 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +12 -0
- data/Guardfile +32 -0
- data/LICENSE.txt +21 -0
- data/README.md +75 -0
- data/Rakefile +15 -0
- data/bin/appraisal +17 -0
- data/bin/console +14 -0
- data/bin/rspec +17 -0
- data/bin/setup +8 -0
- data/gemfiles/rails_4.gemfile +17 -0
- data/gemfiles/rails_5.gemfile +17 -0
- data/graphiti.gemspec +34 -0
- data/lib/generators/jsonapi/resource_generator.rb +169 -0
- data/lib/generators/jsonapi/templates/application_resource.rb.erb +15 -0
- data/lib/generators/jsonapi/templates/controller.rb.erb +61 -0
- data/lib/generators/jsonapi/templates/create_request_spec.rb.erb +30 -0
- data/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +20 -0
- data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +22 -0
- data/lib/generators/jsonapi/templates/resource.rb.erb +11 -0
- data/lib/generators/jsonapi/templates/resource_reads_spec.rb.erb +62 -0
- data/lib/generators/jsonapi/templates/resource_writes_spec.rb.erb +63 -0
- data/lib/generators/jsonapi/templates/show_request_spec.rb.erb +21 -0
- data/lib/generators/jsonapi/templates/update_request_spec.rb.erb +34 -0
- data/lib/graphiti-rb.rb +1 -0
- data/lib/graphiti.rb +121 -0
- data/lib/graphiti/adapters/abstract.rb +516 -0
- data/lib/graphiti/adapters/active_record.rb +6 -0
- data/lib/graphiti/adapters/active_record/base.rb +249 -0
- data/lib/graphiti/adapters/active_record/belongs_to_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/has_many_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/has_one_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/inferrence.rb +12 -0
- data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +30 -0
- data/lib/graphiti/adapters/null.rb +236 -0
- data/lib/graphiti/base.rb +70 -0
- data/lib/graphiti/configuration.rb +21 -0
- data/lib/graphiti/context.rb +16 -0
- data/lib/graphiti/deserializer.rb +208 -0
- data/lib/graphiti/errors.rb +309 -0
- data/lib/graphiti/extensions/boolean_attribute.rb +33 -0
- data/lib/graphiti/extensions/extra_attribute.rb +70 -0
- data/lib/graphiti/extensions/temp_id.rb +26 -0
- data/lib/graphiti/filter_operators.rb +25 -0
- data/lib/graphiti/hash_renderer.rb +57 -0
- data/lib/graphiti/jsonapi_serializable_ext.rb +50 -0
- data/lib/graphiti/query.rb +251 -0
- data/lib/graphiti/rails.rb +28 -0
- data/lib/graphiti/railtie.rb +74 -0
- data/lib/graphiti/renderer.rb +60 -0
- data/lib/graphiti/resource.rb +110 -0
- data/lib/graphiti/resource/configuration.rb +239 -0
- data/lib/graphiti/resource/dsl.rb +138 -0
- data/lib/graphiti/resource/interface.rb +32 -0
- data/lib/graphiti/resource/polymorphism.rb +68 -0
- data/lib/graphiti/resource/sideloading.rb +102 -0
- data/lib/graphiti/resource_proxy.rb +127 -0
- data/lib/graphiti/responders.rb +19 -0
- data/lib/graphiti/runner.rb +25 -0
- data/lib/graphiti/scope.rb +98 -0
- data/lib/graphiti/scoping/base.rb +99 -0
- data/lib/graphiti/scoping/default_filter.rb +58 -0
- data/lib/graphiti/scoping/extra_attributes.rb +29 -0
- data/lib/graphiti/scoping/filter.rb +93 -0
- data/lib/graphiti/scoping/filterable.rb +36 -0
- data/lib/graphiti/scoping/paginate.rb +87 -0
- data/lib/graphiti/scoping/sort.rb +64 -0
- data/lib/graphiti/sideload.rb +281 -0
- data/lib/graphiti/sideload/belongs_to.rb +34 -0
- data/lib/graphiti/sideload/has_many.rb +16 -0
- data/lib/graphiti/sideload/has_one.rb +9 -0
- data/lib/graphiti/sideload/many_to_many.rb +24 -0
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +108 -0
- data/lib/graphiti/stats/dsl.rb +89 -0
- data/lib/graphiti/stats/payload.rb +49 -0
- data/lib/graphiti/types.rb +172 -0
- data/lib/graphiti/util/attribute_check.rb +88 -0
- data/lib/graphiti/util/field_params.rb +16 -0
- data/lib/graphiti/util/hash.rb +51 -0
- data/lib/graphiti/util/hooks.rb +33 -0
- data/lib/graphiti/util/include_params.rb +39 -0
- data/lib/graphiti/util/persistence.rb +219 -0
- data/lib/graphiti/util/relationship_payload.rb +64 -0
- data/lib/graphiti/util/serializer_attributes.rb +97 -0
- data/lib/graphiti/util/sideload.rb +33 -0
- data/lib/graphiti/util/validation_response.rb +78 -0
- data/lib/graphiti/version.rb +3 -0
- metadata +317 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
class Graphiti::Sideload::HasMany < Graphiti::Sideload
|
2
|
+
def type
|
3
|
+
:has_many
|
4
|
+
end
|
5
|
+
|
6
|
+
def load_params(parents, query)
|
7
|
+
query.to_hash.tap do |hash|
|
8
|
+
hash[:filter] ||= {}
|
9
|
+
hash[:filter][foreign_key] = ids_for_parents(parents)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def assign_each(parent, children)
|
14
|
+
children.select { |c| c.send(foreign_key) == parent.send(primary_key) }
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Graphiti::Sideload::ManyToMany < Graphiti::Sideload
|
2
|
+
def type
|
3
|
+
:many_to_many
|
4
|
+
end
|
5
|
+
|
6
|
+
def through
|
7
|
+
foreign_key.keys.first
|
8
|
+
end
|
9
|
+
|
10
|
+
def true_foreign_key
|
11
|
+
foreign_key.values.first
|
12
|
+
end
|
13
|
+
|
14
|
+
def infer_foreign_key
|
15
|
+
raise 'You must explicitly pass :foreign_key for many-to-many relaitonships, or override in subclass to return a hash.'
|
16
|
+
end
|
17
|
+
|
18
|
+
def assign_each(parent, children)
|
19
|
+
children.select do |c|
|
20
|
+
match = ->(ct) { ct.send(true_foreign_key) == parent.send(primary_key) }
|
21
|
+
c.send(through).any?(&match)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
class Graphiti::Sideload::PolymorphicBelongsTo < Graphiti::Sideload::BelongsTo
|
2
|
+
class Group
|
3
|
+
attr_reader :name, :calls
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
@calls = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(name, *args, &blk)
|
11
|
+
@calls << [name, args, blk]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Grouper
|
16
|
+
attr_reader :field_name
|
17
|
+
|
18
|
+
def initialize(field_name)
|
19
|
+
@field_name = field_name
|
20
|
+
@groups = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def on(name, &blk)
|
24
|
+
group = Group.new(name)
|
25
|
+
@groups << group
|
26
|
+
group.belongs_to(name.to_s.underscore.to_sym)
|
27
|
+
group
|
28
|
+
end
|
29
|
+
|
30
|
+
def apply(sideload, resource_class)
|
31
|
+
@groups.each do |group|
|
32
|
+
group.calls.each do |call|
|
33
|
+
args = call[1]
|
34
|
+
opts = args.extract_options!
|
35
|
+
opts.merge! as: sideload.name,
|
36
|
+
parent: sideload,
|
37
|
+
group_name: group.name,
|
38
|
+
polymorphic_child: true
|
39
|
+
if !sideload.resource.class.abstract_class?
|
40
|
+
opts[:foreign_key] ||= sideload.foreign_key
|
41
|
+
opts[:primary_key] ||= sideload.primary_key
|
42
|
+
end
|
43
|
+
args << opts
|
44
|
+
resource_class.send(call[0], *args, &call[2])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class_attribute :grouper
|
51
|
+
attr_accessor :children
|
52
|
+
self.grouper = Grouper.new(:default)
|
53
|
+
|
54
|
+
def type
|
55
|
+
:polymorphic_belongs_to
|
56
|
+
end
|
57
|
+
|
58
|
+
def infer_foreign_key
|
59
|
+
:"#{name}_id"
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.group_by(name, &blk)
|
63
|
+
self.grouper = Grouper.new(name)
|
64
|
+
self.grouper.instance_eval(&blk)
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(name, opts)
|
68
|
+
super
|
69
|
+
self.children = {}
|
70
|
+
grouper.apply(self, parent_resource_class)
|
71
|
+
end
|
72
|
+
|
73
|
+
def child_for_type(type)
|
74
|
+
children.values.find do |sideload|
|
75
|
+
sideload.resource.type == type
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def resolve(parents, query)
|
80
|
+
parents.group_by(&grouper.field_name).each_pair do |group_name, group|
|
81
|
+
next if group_name.nil?
|
82
|
+
|
83
|
+
match = ->(name, sl) { sl.group_name == group_name.to_sym }
|
84
|
+
if child = children.find(&match)
|
85
|
+
sideload = child[1]
|
86
|
+
query = remove_invalid_sideloads(sideload.resource, query)
|
87
|
+
sideload.resolve(group, query)
|
88
|
+
else
|
89
|
+
err = ::Graphiti::Errors::PolymorphicChildNotFound
|
90
|
+
raise err.new(self, group_name)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# We may be requesting a relationship that some subclasses support,
|
98
|
+
# but not others. Remove anything we don't support.
|
99
|
+
def remove_invalid_sideloads(resource, query)
|
100
|
+
query = query.dup
|
101
|
+
query.sideloads.each_pair do |key, value|
|
102
|
+
unless resource.class.sideload(key)
|
103
|
+
query.sideloads.delete(key)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
query
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Graphiti
|
2
|
+
module Stats
|
3
|
+
# Provides an easier interface to stats scoping.
|
4
|
+
#
|
5
|
+
# Used within Resource DSL:
|
6
|
+
#
|
7
|
+
# allow_stat total: [:count] do
|
8
|
+
# # ... eval'd in Stats::DSL context! ...
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# This allows us to define arbitrary stats:
|
12
|
+
#
|
13
|
+
# allow_stat total: [:count] do
|
14
|
+
# standard_deviation { |scope, attr| ... }
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# And use convenience methods:
|
18
|
+
#
|
19
|
+
# allow_stat :rating do
|
20
|
+
# count!
|
21
|
+
# average!
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @see Resource.allow_stat
|
25
|
+
# @attr_reader [Symbol] name the stat, e.g. :total
|
26
|
+
# @attr_reader [Hash] calculations procs for various metrics
|
27
|
+
class DSL
|
28
|
+
attr_reader :name, :calculations
|
29
|
+
|
30
|
+
# @param [Adapters::Abstract] adapter the Resource adapter
|
31
|
+
# @param [Symbol, Hash] config example: +:total+ or +{ total: [:count] }+
|
32
|
+
def initialize(adapter, config)
|
33
|
+
config = { config => [] } if config.is_a?(Symbol)
|
34
|
+
|
35
|
+
@adapter = adapter
|
36
|
+
@calculations = {}
|
37
|
+
@name = config.keys.first
|
38
|
+
Array(config.values.first).each { |c| send(:"#{c}!") }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Used for defining arbitrary stats within the DSL:
|
42
|
+
#
|
43
|
+
# allow_stat :total do
|
44
|
+
# standard_deviation { |scope, attr| ... }
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# ...will hit +method_missing+ and store the proc for future reference.
|
48
|
+
# @api private
|
49
|
+
def method_missing(meth, *args, &blk)
|
50
|
+
@calculations[meth] = blk
|
51
|
+
end
|
52
|
+
|
53
|
+
# Grab a calculation proc. Raises error if no corresponding stat
|
54
|
+
# has been configured.
|
55
|
+
#
|
56
|
+
# @param [String, Symbol] name the name of the calculation, e.g. +:total+
|
57
|
+
# @return [Proc] the proc to run the calculation
|
58
|
+
def calculation(name)
|
59
|
+
callable = @calculations[name] || @calculations[name.to_sym]
|
60
|
+
callable || raise(Errors::StatNotFound.new(@name, name))
|
61
|
+
end
|
62
|
+
|
63
|
+
# Convenience method for default :count proc
|
64
|
+
def count!
|
65
|
+
@calculations[:count] = @adapter.method(:count)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Convenience method for default :sum proc
|
69
|
+
def sum!
|
70
|
+
@calculations[:sum] = @adapter.method(:sum)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Convenience method for default :average proc
|
74
|
+
def average!
|
75
|
+
@calculations[:average] = @adapter.method(:average)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Convenience method for default :maximum proc
|
79
|
+
def maximum!
|
80
|
+
@calculations[:maximum] = @adapter.method(:maximum)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Convenience method for default :minimum proc
|
84
|
+
def minimum!
|
85
|
+
@calculations[:minimum] = @adapter.method(:minimum)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Graphiti
|
2
|
+
module Stats
|
3
|
+
# Generate the stats payload so we can return it in the response.
|
4
|
+
#
|
5
|
+
# {
|
6
|
+
# data: [...],
|
7
|
+
# meta: { stats: the_generated_payload }
|
8
|
+
# }
|
9
|
+
#
|
10
|
+
# For example:
|
11
|
+
#
|
12
|
+
# {
|
13
|
+
# data: [...],
|
14
|
+
# meta: { stats: { total: { count: 100 } } }
|
15
|
+
# }
|
16
|
+
class Payload
|
17
|
+
def initialize(resource, query, scope)
|
18
|
+
@resource = resource
|
19
|
+
@query = query
|
20
|
+
@scope = scope
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generate the payload for +{ meta: { stats: { ... } } }+
|
24
|
+
# Loops over all calculations, computes then, and gives back
|
25
|
+
# a hash of stats and their results.
|
26
|
+
# @return [Hash] the generated payload
|
27
|
+
def generate
|
28
|
+
{}.tap do |stats|
|
29
|
+
@query.stats.each_pair do |name, calculation|
|
30
|
+
stats[name] = {}
|
31
|
+
|
32
|
+
each_calculation(name, calculation) do |calc, function|
|
33
|
+
stats[name][calc] = function.call(@scope, name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def each_calculation(name, calculations)
|
42
|
+
calculations.each do |calc|
|
43
|
+
function = @resource.stat(name, calc)
|
44
|
+
yield calc, function
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class Types
|
3
|
+
def self.create(primitive, &blk)
|
4
|
+
definition = Dry::Types::Definition.new(primitive)
|
5
|
+
definition.constructor(&blk)
|
6
|
+
end
|
7
|
+
|
8
|
+
WriteDateTime = create(::DateTime) do |input|
|
9
|
+
if input.is_a?(::Date) || input.is_a?(::Time)
|
10
|
+
input = ::DateTime.parse(input.to_s)
|
11
|
+
end
|
12
|
+
input = Dry::Types['json.date_time'][input]
|
13
|
+
Dry::Types['strict.date_time'][input] if input
|
14
|
+
end
|
15
|
+
|
16
|
+
ReadDateTime = create(::DateTime) do |input|
|
17
|
+
if input.is_a?(::Date) || input.is_a?(::Time)
|
18
|
+
input = ::DateTime.parse(input.to_s)
|
19
|
+
end
|
20
|
+
input = Dry::Types['json.date_time'][input]
|
21
|
+
Dry::Types['strict.date_time'][input].iso8601 if input
|
22
|
+
end
|
23
|
+
|
24
|
+
PresentParamsDateTime = create(::DateTime) do |input|
|
25
|
+
input = Dry::Types['params.date_time'][input]
|
26
|
+
Dry::Types['strict.date_time'][input]
|
27
|
+
end
|
28
|
+
|
29
|
+
Date = create(::Date) do |input|
|
30
|
+
input = ::Date.parse(input.to_s) if input.is_a?(::Time)
|
31
|
+
input = Dry::Types['json.date'][input]
|
32
|
+
Dry::Types['strict.date'][input] if input
|
33
|
+
end
|
34
|
+
|
35
|
+
PresentDate = create(::Date) do |input|
|
36
|
+
input = ::Date.parse(input.to_s) if input.is_a?(::Time)
|
37
|
+
input = Dry::Types['json.date'][input]
|
38
|
+
Dry::Types['strict.date'][input]
|
39
|
+
end
|
40
|
+
|
41
|
+
Bool = create(nil) do |input|
|
42
|
+
input = Dry::Types['params.bool'][input]
|
43
|
+
Dry::Types['strict.bool'][input] if input
|
44
|
+
end
|
45
|
+
|
46
|
+
PresentBool = create(nil) do |input|
|
47
|
+
input = Dry::Types['params.bool'][input]
|
48
|
+
Dry::Types['strict.bool'][input]
|
49
|
+
end
|
50
|
+
|
51
|
+
Integer = create(::Integer) do |input|
|
52
|
+
Dry::Types['coercible.integer'][input] if input
|
53
|
+
end
|
54
|
+
|
55
|
+
# The Float() check here is to ensure we have a number
|
56
|
+
# Otherwise BigDecimal('foo') *will return a decimal*
|
57
|
+
ParamDecimal = create(::BigDecimal) do |input|
|
58
|
+
Float(input)
|
59
|
+
input = Dry::Types['coercible.decimal'][input]
|
60
|
+
Dry::Types['strict.decimal'][input]
|
61
|
+
end
|
62
|
+
|
63
|
+
PresentInteger = create(::Integer) do |input|
|
64
|
+
Dry::Types['coercible.integer'][input]
|
65
|
+
end
|
66
|
+
|
67
|
+
Float = create(::Float) do |input|
|
68
|
+
Dry::Types['coercible.float'][input] if input
|
69
|
+
end
|
70
|
+
|
71
|
+
PresentParamsHash = create(::Hash) do |input|
|
72
|
+
Dry::Types['params.hash'][input].deep_symbolize_keys
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.map
|
76
|
+
@map ||= begin
|
77
|
+
hash = {
|
78
|
+
integer_id: {
|
79
|
+
canonical_name: :integer,
|
80
|
+
params: Dry::Types['coercible.integer'],
|
81
|
+
read: Dry::Types['coercible.string'],
|
82
|
+
write: Dry::Types['coercible.integer']
|
83
|
+
},
|
84
|
+
string: {
|
85
|
+
params: Dry::Types['coercible.string'],
|
86
|
+
read: Dry::Types['coercible.string'],
|
87
|
+
write: Dry::Types['coercible.string']
|
88
|
+
},
|
89
|
+
integer: {
|
90
|
+
params: PresentInteger,
|
91
|
+
read: Integer,
|
92
|
+
write: Integer
|
93
|
+
},
|
94
|
+
big_decimal: {
|
95
|
+
params: ParamDecimal,
|
96
|
+
read: Dry::Types['json.decimal'],
|
97
|
+
write: Dry::Types['json.decimal']
|
98
|
+
},
|
99
|
+
float: {
|
100
|
+
params: Dry::Types['coercible.float'],
|
101
|
+
read: Float,
|
102
|
+
write: Float
|
103
|
+
},
|
104
|
+
boolean: {
|
105
|
+
params: PresentBool,
|
106
|
+
read: Bool,
|
107
|
+
write: Bool
|
108
|
+
},
|
109
|
+
date: {
|
110
|
+
params: PresentDate,
|
111
|
+
read: Date,
|
112
|
+
write: Date
|
113
|
+
},
|
114
|
+
datetime: {
|
115
|
+
params: PresentParamsDateTime,
|
116
|
+
read: ReadDateTime,
|
117
|
+
write: WriteDateTime
|
118
|
+
},
|
119
|
+
hash: {
|
120
|
+
params: PresentParamsHash,
|
121
|
+
read: Dry::Types['strict.hash'],
|
122
|
+
write: Dry::Types['strict.hash']
|
123
|
+
},
|
124
|
+
array: {
|
125
|
+
params: Dry::Types['strict.array'],
|
126
|
+
read: Dry::Types['strict.array'],
|
127
|
+
write: Dry::Types['strict.array']
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
hash.each_pair do |k, v|
|
132
|
+
hash[k][:canonical_name] ||= k
|
133
|
+
end
|
134
|
+
|
135
|
+
arrays = {}
|
136
|
+
hash.each_pair do |name, map|
|
137
|
+
arrays[:"array_of_#{name.to_s.pluralize}"] = {
|
138
|
+
canonical_name: name,
|
139
|
+
params: Dry::Types['strict.array'].of(map[:params]),
|
140
|
+
read: Dry::Types['strict.array'].of(map[:read]),
|
141
|
+
test: Dry::Types['strict.array'].of(map[:test]),
|
142
|
+
write: Dry::Types['strict.array'].of(map[:write])
|
143
|
+
}
|
144
|
+
end
|
145
|
+
hash.merge!(arrays)
|
146
|
+
|
147
|
+
hash
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.[](key)
|
152
|
+
map[key.to_sym]
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.[]=(key, value)
|
156
|
+
unless value.is_a?(Hash)
|
157
|
+
value = {
|
158
|
+
read: value,
|
159
|
+
params: value,
|
160
|
+
test: value
|
161
|
+
}
|
162
|
+
end
|
163
|
+
map[key.to_sym] = value
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.name_for(key)
|
167
|
+
key = key.to_sym
|
168
|
+
type = map[key]
|
169
|
+
type[:canonical_name]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|