jsonapionify 0.0.1.pre
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/.codeclimate.yml +29 -0
- data/.csslintrc +2 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +1171 -0
- data/.ruby-version +1 -0
- data/.travis.yml +10 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/Guardfile +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +34 -0
- data/TODO +13 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/config.ru +15 -0
- data/fixtures/documentation.json +364 -0
- data/jsonapionify.gemspec +50 -0
- data/lib/core_ext/boolean.rb +3 -0
- data/lib/jsonapionify/api/action.rb +211 -0
- data/lib/jsonapionify/api/attribute.rb +67 -0
- data/lib/jsonapionify/api/base/app_builder.rb +33 -0
- data/lib/jsonapionify/api/base/class_methods.rb +73 -0
- data/lib/jsonapionify/api/base/delegation.rb +15 -0
- data/lib/jsonapionify/api/base/doc_helper.rb +47 -0
- data/lib/jsonapionify/api/base/reloader.rb +10 -0
- data/lib/jsonapionify/api/base/resource_definitions.rb +39 -0
- data/lib/jsonapionify/api/base.rb +25 -0
- data/lib/jsonapionify/api/context.rb +14 -0
- data/lib/jsonapionify/api/context_delegate.rb +42 -0
- data/lib/jsonapionify/api/errors.rb +6 -0
- data/lib/jsonapionify/api/errors_object.rb +66 -0
- data/lib/jsonapionify/api/header_options.rb +13 -0
- data/lib/jsonapionify/api/param_options.rb +46 -0
- data/lib/jsonapionify/api/relationship/blocks.rb +41 -0
- data/lib/jsonapionify/api/relationship/many.rb +61 -0
- data/lib/jsonapionify/api/relationship/one.rb +36 -0
- data/lib/jsonapionify/api/relationship.rb +89 -0
- data/lib/jsonapionify/api/resource/builders.rb +81 -0
- data/lib/jsonapionify/api/resource/class_methods.rb +82 -0
- data/lib/jsonapionify/api/resource/defaults/actions.rb +11 -0
- data/lib/jsonapionify/api/resource/defaults/errors.rb +99 -0
- data/lib/jsonapionify/api/resource/defaults/request_contexts.rb +96 -0
- data/lib/jsonapionify/api/resource/defaults/response_contexts.rb +31 -0
- data/lib/jsonapionify/api/resource/defaults.rb +10 -0
- data/lib/jsonapionify/api/resource/definitions/actions.rb +196 -0
- data/lib/jsonapionify/api/resource/definitions/attributes.rb +51 -0
- data/lib/jsonapionify/api/resource/definitions/contexts.rb +16 -0
- data/lib/jsonapionify/api/resource/definitions/helpers.rb +9 -0
- data/lib/jsonapionify/api/resource/definitions/pagination.rb +79 -0
- data/lib/jsonapionify/api/resource/definitions/params.rb +49 -0
- data/lib/jsonapionify/api/resource/definitions/relationships.rb +42 -0
- data/lib/jsonapionify/api/resource/definitions/request_headers.rb +103 -0
- data/lib/jsonapionify/api/resource/definitions/response_headers.rb +22 -0
- data/lib/jsonapionify/api/resource/definitions/scopes.rb +50 -0
- data/lib/jsonapionify/api/resource/definitions/sorting.rb +85 -0
- data/lib/jsonapionify/api/resource/definitions.rb +14 -0
- data/lib/jsonapionify/api/resource/error_handling.rb +108 -0
- data/lib/jsonapionify/api/resource/http.rb +11 -0
- data/lib/jsonapionify/api/resource/includer.rb +4 -0
- data/lib/jsonapionify/api/resource.rb +35 -0
- data/lib/jsonapionify/api/response.rb +47 -0
- data/lib/jsonapionify/api/server/mock_response.rb +37 -0
- data/lib/jsonapionify/api/server/request.rb +78 -0
- data/lib/jsonapionify/api/server.rb +50 -0
- data/lib/jsonapionify/api/test_helper.rb +52 -0
- data/lib/jsonapionify/api.rb +9 -0
- data/lib/jsonapionify/autoload.rb +52 -0
- data/lib/jsonapionify/callbacks.rb +49 -0
- data/lib/jsonapionify/character_range.rb +41 -0
- data/lib/jsonapionify/continuation.rb +26 -0
- data/lib/jsonapionify/documentation/template.erb +487 -0
- data/lib/jsonapionify/documentation.rb +40 -0
- data/lib/jsonapionify/enumerable_observer.rb +91 -0
- data/lib/jsonapionify/indented_string.rb +27 -0
- data/lib/jsonapionify/inherited_attributes.rb +125 -0
- data/lib/jsonapionify/structure/collections/base.rb +104 -0
- data/lib/jsonapionify/structure/collections/errors.rb +7 -0
- data/lib/jsonapionify/structure/collections/included_resources.rb +39 -0
- data/lib/jsonapionify/structure/collections/resource_identifiers.rb +7 -0
- data/lib/jsonapionify/structure/collections/resources.rb +7 -0
- data/lib/jsonapionify/structure/helpers/errors.rb +71 -0
- data/lib/jsonapionify/structure/helpers/inherits_origin.rb +17 -0
- data/lib/jsonapionify/structure/helpers/member_names.rb +37 -0
- data/lib/jsonapionify/structure/helpers/meta_delegate.rb +16 -0
- data/lib/jsonapionify/structure/helpers/object_defaults.rb +123 -0
- data/lib/jsonapionify/structure/helpers/object_setters.rb +21 -0
- data/lib/jsonapionify/structure/helpers/pagination_links.rb +10 -0
- data/lib/jsonapionify/structure/helpers/validations.rb +296 -0
- data/lib/jsonapionify/structure/maps/base.rb +25 -0
- data/lib/jsonapionify/structure/maps/error_links.rb +7 -0
- data/lib/jsonapionify/structure/maps/links.rb +21 -0
- data/lib/jsonapionify/structure/maps/relationship_links.rb +11 -0
- data/lib/jsonapionify/structure/maps/relationships.rb +23 -0
- data/lib/jsonapionify/structure/maps/resource_links.rb +7 -0
- data/lib/jsonapionify/structure/maps/top_level_links.rb +10 -0
- data/lib/jsonapionify/structure/objects/attributes.rb +29 -0
- data/lib/jsonapionify/structure/objects/base.rb +166 -0
- data/lib/jsonapionify/structure/objects/error.rb +16 -0
- data/lib/jsonapionify/structure/objects/included_resource.rb +14 -0
- data/lib/jsonapionify/structure/objects/jsonapi.rb +7 -0
- data/lib/jsonapionify/structure/objects/link.rb +18 -0
- data/lib/jsonapionify/structure/objects/meta.rb +7 -0
- data/lib/jsonapionify/structure/objects/relationship.rb +20 -0
- data/lib/jsonapionify/structure/objects/resource.rb +45 -0
- data/lib/jsonapionify/structure/objects/resource_identifier.rb +40 -0
- data/lib/jsonapionify/structure/objects/source.rb +10 -0
- data/lib/jsonapionify/structure/objects/top_level.rb +105 -0
- data/lib/jsonapionify/structure.rb +27 -0
- data/lib/jsonapionify/types/array_type.rb +32 -0
- data/lib/jsonapionify/types/boolean_type.rb +22 -0
- data/lib/jsonapionify/types/date_string_type.rb +28 -0
- data/lib/jsonapionify/types/float_type.rb +8 -0
- data/lib/jsonapionify/types/integer_type.rb +9 -0
- data/lib/jsonapionify/types/object_type.rb +22 -0
- data/lib/jsonapionify/types/string_type.rb +66 -0
- data/lib/jsonapionify/types/time_string_type.rb +28 -0
- data/lib/jsonapionify/types.rb +49 -0
- data/lib/jsonapionify/unstrict_proc.rb +28 -0
- data/lib/jsonapionify/version.rb +3 -0
- data/lib/jsonapionify.rb +37 -0
- metadata +530 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
module JSONAPIonify
|
|
2
|
+
module InheritedAttributes
|
|
3
|
+
|
|
4
|
+
def inherited_hash_attribute(*attrs, instance_reader: true, instance_writer: true, instance_accessor: true)
|
|
5
|
+
instance_reader, instance_writer = false, false unless instance_accessor
|
|
6
|
+
attrs.each do |attr|
|
|
7
|
+
ivar = :"@#{attr}"
|
|
8
|
+
setter = :"#{attr}="
|
|
9
|
+
getter = :"#{attr}"
|
|
10
|
+
|
|
11
|
+
# Define First Variable
|
|
12
|
+
instance_variable_set(ivar, Hash.new)
|
|
13
|
+
|
|
14
|
+
# Define Setter
|
|
15
|
+
define_singleton_method setter do |value|
|
|
16
|
+
instance_variable_get(ivar).tap(&:clear).merge! value
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Define Getter
|
|
20
|
+
define_singleton_method getter do
|
|
21
|
+
instance_variable_get(ivar)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Define Instance Getter
|
|
25
|
+
define_method getter do
|
|
26
|
+
if instance_variable_defined?(ivar)
|
|
27
|
+
instance_variable_get(ivar)
|
|
28
|
+
else
|
|
29
|
+
instance_variable_set(
|
|
30
|
+
ivar,
|
|
31
|
+
self.class.instance_variable_get(ivar).dup
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end if instance_reader
|
|
35
|
+
|
|
36
|
+
# Define Instance Setter
|
|
37
|
+
define_method setter do |value|
|
|
38
|
+
instance_variable_get(ivar).tap(&:clear).merge! value
|
|
39
|
+
end if instance_writer
|
|
40
|
+
|
|
41
|
+
# Define Inheritor
|
|
42
|
+
mod = Module.new do
|
|
43
|
+
define_method :inherited do |subclass|
|
|
44
|
+
super(subclass)
|
|
45
|
+
|
|
46
|
+
# Set subclass variable
|
|
47
|
+
subclass.instance_variable_set(ivar, Hash.new)
|
|
48
|
+
parent_var = instance_variable_get(ivar)
|
|
49
|
+
subclass_var = subclass.instance_variable_get(ivar)
|
|
50
|
+
|
|
51
|
+
# Merge changes from parent
|
|
52
|
+
subclass_var.deep_merge!(parent_var)
|
|
53
|
+
|
|
54
|
+
# Observe
|
|
55
|
+
observer = EnumerableObserver.observe(parent_var)
|
|
56
|
+
observer.added { |items| subclass_var.deep_merge! items.to_h }
|
|
57
|
+
observer.removed { |items| subclass_var.delete_if { |k, v| items.to_h[k] == v } }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
extend mod
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def inherited_array_attribute(*attrs, instance_reader: true, instance_writer: true, instance_accessor: true)
|
|
65
|
+
instance_reader, instance_writer = false, false unless instance_accessor
|
|
66
|
+
attrs.each do |attr|
|
|
67
|
+
ivar = :"@#{attr}"
|
|
68
|
+
setter = :"#{attr}="
|
|
69
|
+
getter = :"#{attr}"
|
|
70
|
+
|
|
71
|
+
# Define First Variable
|
|
72
|
+
instance_variable_set(ivar, Array.new)
|
|
73
|
+
|
|
74
|
+
# Define Setter
|
|
75
|
+
define_singleton_method setter do |value|
|
|
76
|
+
instance_variable_get(ivar).tap(&:clear).concat(value)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Define Getter
|
|
80
|
+
define_singleton_method getter do
|
|
81
|
+
instance_variable_get(ivar)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Define Instance Getter
|
|
85
|
+
define_method getter do
|
|
86
|
+
if instance_variable_defined?(ivar)
|
|
87
|
+
instance_variable_get(ivar)
|
|
88
|
+
else
|
|
89
|
+
instance_variable_set(
|
|
90
|
+
ivar,
|
|
91
|
+
self.class.instance_variable_get(ivar).dup
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
end if instance_reader
|
|
95
|
+
|
|
96
|
+
# Define Instance Setter
|
|
97
|
+
define_method setter do |value|
|
|
98
|
+
instance_variable_get(ivar).tap(&:clear).concat(value)
|
|
99
|
+
end if instance_writer
|
|
100
|
+
|
|
101
|
+
# Define Inheritor
|
|
102
|
+
mod = Module.new do
|
|
103
|
+
define_method :inherited do |subclass|
|
|
104
|
+
super(subclass)
|
|
105
|
+
|
|
106
|
+
# Set subclass variable
|
|
107
|
+
subclass.instance_variable_set(ivar, Array.new)
|
|
108
|
+
parent_var = instance_variable_get(ivar)
|
|
109
|
+
subclass_var = subclass.instance_variable_get(ivar)
|
|
110
|
+
|
|
111
|
+
# Merge changes from parent
|
|
112
|
+
subclass_var.concat(parent_var)
|
|
113
|
+
|
|
114
|
+
# Observe
|
|
115
|
+
observer = EnumerableObserver.observe(parent_var)
|
|
116
|
+
observer.added { |items| subclass_var.concat items }
|
|
117
|
+
observer.removed { |items| items.each { |item| subclass_var.delete item } }
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
extend mod
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
|
2
|
+
|
|
3
|
+
module JSONAPIonify::Structure
|
|
4
|
+
module Collections
|
|
5
|
+
class Base < Array
|
|
6
|
+
include JSONAPIonify::EnumerableObserver
|
|
7
|
+
include Helpers::InheritsOrigin
|
|
8
|
+
attr_reader :parent
|
|
9
|
+
|
|
10
|
+
delegate :cache_store, to: JSONAPIonify
|
|
11
|
+
|
|
12
|
+
def self.value_is(type_class)
|
|
13
|
+
define_method(:type_class) do
|
|
14
|
+
type_class
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
value_is Objects::Base
|
|
19
|
+
|
|
20
|
+
def initialize(array = [])
|
|
21
|
+
observe.added do |items|
|
|
22
|
+
items.each do |item|
|
|
23
|
+
item.instance_variable_set(:@parent, self) unless item.frozen?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
array.each do |instance|
|
|
27
|
+
self << instance
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def original_method(method)
|
|
32
|
+
Array.instance_method(method).bind(self)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def validate
|
|
36
|
+
each do |member|
|
|
37
|
+
member.validate if member.respond_to? :validate
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def signature
|
|
42
|
+
"#{self.class.name}:#{Digest::SHA2.hexdigest map(&:signature).join}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def collect_hashes
|
|
46
|
+
map do |member|
|
|
47
|
+
case member
|
|
48
|
+
when Objects::Base, Hash
|
|
49
|
+
member.to_hash
|
|
50
|
+
else
|
|
51
|
+
member
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def compile
|
|
57
|
+
collect_hashes
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def new(**attributes)
|
|
61
|
+
self << attributes
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
alias_method :append, :new
|
|
65
|
+
|
|
66
|
+
def <<(instance)
|
|
67
|
+
new_instance =
|
|
68
|
+
case instance
|
|
69
|
+
when Hash
|
|
70
|
+
type_class.new(**instance)
|
|
71
|
+
when type_class
|
|
72
|
+
instance
|
|
73
|
+
else
|
|
74
|
+
raise ValidationError,
|
|
75
|
+
" Can 't initialize collection `#{self.class.name}` with a type of `#{instance.class.name}`"
|
|
76
|
+
end
|
|
77
|
+
super new_instance
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def errors
|
|
81
|
+
map.each_with_index.each_with_object({}) do |(value, key), errors|
|
|
82
|
+
next unless value.respond_to? :errors
|
|
83
|
+
value.errors.each do |error_key, message|
|
|
84
|
+
errors[[key, error_key].join('/')] = message
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def warnings
|
|
90
|
+
map.each_with_index.each_with_object({}) do |(value, key), warnings|
|
|
91
|
+
next unless value.respond_to? :all_warnings
|
|
92
|
+
value.all_warnings.each do |warning_key, message|
|
|
93
|
+
warnings[[key, warning_key].join('. ')] = message
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
alias_method :all_warnings, :warnings
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module JSONAPIonify::Structure
|
|
2
|
+
module Collections
|
|
3
|
+
class IncludedResources < Resources
|
|
4
|
+
value_is Objects::IncludedResource
|
|
5
|
+
|
|
6
|
+
def self.referenceable?(referenced, not_referenced)
|
|
7
|
+
not_referenced.any? do |unreferenced_item|
|
|
8
|
+
referenced.any? do |referenced_item|
|
|
9
|
+
referenced_item.relates_to? unreferenced_item
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def referenced
|
|
15
|
+
return [] unless parent
|
|
16
|
+
top_level_referenced.tap do |referenced|
|
|
17
|
+
not_referenced = reduce([]) { |a, i| a << i }
|
|
18
|
+
|
|
19
|
+
while self.class.referenceable?(referenced, not_referenced)
|
|
20
|
+
not_referenced.each do |resource|
|
|
21
|
+
if referenced.any? { |referenced_item| referenced_item.relates_to? resource }
|
|
22
|
+
referenced << not_referenced.delete(resource)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def top_level_referenced
|
|
32
|
+
return [] unless parent
|
|
33
|
+
reduce([]) { |a, i| a << i }.select do |resource|
|
|
34
|
+
Array.wrap(parent[:data]).any? { |tlr| tlr.relates_to? resource }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
|
2
|
+
|
|
3
|
+
module JSONAPIonify::Structure
|
|
4
|
+
module Helpers
|
|
5
|
+
class Errors
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
delegate :clear, :present?, :blank?, :each, to: :@hash
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@hash = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize_copy(new_copy)
|
|
15
|
+
super
|
|
16
|
+
instance_variables.each do |ivar|
|
|
17
|
+
new_copy.instance_variable_set(
|
|
18
|
+
ivar, instance_variable_get(ivar).dup
|
|
19
|
+
) rescue nil
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add(key, value)
|
|
24
|
+
(@hash[key] ||= []) << value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
alias_method :[]=, :add
|
|
28
|
+
|
|
29
|
+
def [](key)
|
|
30
|
+
@hash[key]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def replace(key, messages)
|
|
34
|
+
@hash[key] = Array.wrap(messages)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def all_messages
|
|
38
|
+
@hash.each_with_object([]) do |(key, errors), messages|
|
|
39
|
+
errors.each do |error|
|
|
40
|
+
messages << [backtick_key(key), error].join(' ')
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def as_collection
|
|
46
|
+
each_with_object(Collections::Errors.new) do |(pointer, messages), collection|
|
|
47
|
+
*, key = pointer.to_s.split('/')
|
|
48
|
+
entity = key.present? ? key : 'object'
|
|
49
|
+
messages.each do |message|
|
|
50
|
+
message = "#{entity} #{message}".chomp('.') << '.'
|
|
51
|
+
object = {
|
|
52
|
+
source: {
|
|
53
|
+
pointer: pointer
|
|
54
|
+
},
|
|
55
|
+
detail: message,
|
|
56
|
+
status: "422"
|
|
57
|
+
}
|
|
58
|
+
collection << object
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def backtick_key(key)
|
|
66
|
+
"`#{key}`"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module JSONAPIonify::Structure
|
|
2
|
+
module Helpers
|
|
3
|
+
module MemberNames
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
def valid?(value)
|
|
7
|
+
return false if value.nil?
|
|
8
|
+
value = value.to_s if value.is_a? Symbol
|
|
9
|
+
[
|
|
10
|
+
value.present?,
|
|
11
|
+
valid_ends?(value),
|
|
12
|
+
contains_valid_characters?(value),
|
|
13
|
+
!contains_invalid_characters?(value)
|
|
14
|
+
].reduce(:&)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def contains_valid_characters?(value)
|
|
20
|
+
value =~ /\A[a-zA-Z0-9\u0080-\uFFFF_\s-]+\Z/
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def valid_ends?(value)
|
|
24
|
+
['-', '_', ' '].map do |char|
|
|
25
|
+
!value.start_with?(char) & !value.end_with?(char)
|
|
26
|
+
end.reduce(:&)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def contains_invalid_characters?(value)
|
|
30
|
+
%w{+ , . [ ] ! " # $ % & ' ( ) * / : ; < = > ? @ \\ ^ ` { } | ~}.map do |char|
|
|
31
|
+
value.include? char
|
|
32
|
+
end.reduce(:|)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class JSONAPIonify::Structure::Helpers::MetaDelegate
|
|
2
|
+
attr_reader :object
|
|
3
|
+
|
|
4
|
+
def initialize(object)
|
|
5
|
+
@object = object
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def []=(k, v)
|
|
9
|
+
object[:meta] ||= {}
|
|
10
|
+
object[:meta][k] = v
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def [](k)
|
|
14
|
+
object[:meta] && object[:meta][k]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
|
|
3
|
+
module JSONAPIonify::Structure
|
|
4
|
+
module Helpers
|
|
5
|
+
module ObjectDefaults
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
include JSONAPIonify::EnumerableObserver
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
# Forces the setter to a type
|
|
11
|
+
def implements(key, as:, **opts)
|
|
12
|
+
type_of! key, must_be: as, allow_nil: true
|
|
13
|
+
track_implementation key, as, **opts
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Forces the setter to the collection type
|
|
17
|
+
# Allows unset types to expose collection and type_map methods
|
|
18
|
+
def collects(key, as:, **opts)
|
|
19
|
+
type_of! key, must_be: as
|
|
20
|
+
track_collection key, as, **opts
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# implements_or_collects(:data, implements: A, collects: B, if: ->(obj){ obj.has_key? :attributes })
|
|
24
|
+
# implements_or_collects(:data, implements: C, collects: D, unless: ->(obj){ obj.has_key? :attributes })
|
|
25
|
+
def collects_or_implements(key, implements:, collects:, allow_nil: false, **opts)
|
|
26
|
+
allowed = [implements, collects]
|
|
27
|
+
allowed << NilClass if allow_nil
|
|
28
|
+
type_of! key, must_be: allowed
|
|
29
|
+
track_collection key, collects, **opts
|
|
30
|
+
track_implementation key, implements, **opts
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
alias_method :implements_or_collects, :collects_or_implements
|
|
34
|
+
|
|
35
|
+
# Defaults
|
|
36
|
+
def default(key, &block)
|
|
37
|
+
after_initialize do
|
|
38
|
+
self[key] ||= instance_eval(&block)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def track_implementation(key, klass, **opts)
|
|
45
|
+
(implementations[key] ||= {})[klass] = opts
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def track_collection(key, klass, **opts)
|
|
49
|
+
(collections[key] ||= {})[klass] = opts
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
included do
|
|
54
|
+
include JSONAPIonify::InheritedAttributes
|
|
55
|
+
# Class Attributes
|
|
56
|
+
attr_reader :unset
|
|
57
|
+
|
|
58
|
+
inherited_hash_attribute :implementations, :collections
|
|
59
|
+
|
|
60
|
+
before_initialize do
|
|
61
|
+
@unset = {}
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def [](k)
|
|
66
|
+
if has_key? k
|
|
67
|
+
super
|
|
68
|
+
elsif collections[k]
|
|
69
|
+
@unset[k] ||= [].tap do |ary|
|
|
70
|
+
observe(ary).added { self[k] = ary }
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def []=(k, v)
|
|
78
|
+
unset.delete_if { |unset_key, _| unset_key == k }
|
|
79
|
+
super k, coerce_value(k, v)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def object
|
|
83
|
+
super.tap do
|
|
84
|
+
newly_set = unset.select { |_, v| v.present? }
|
|
85
|
+
newly_set.each do |k, v|
|
|
86
|
+
self[k] = v
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def coerce_value(k, v)
|
|
94
|
+
if implementations[k] && v.is_a?(Hash)
|
|
95
|
+
coerce_implementation(k, v)
|
|
96
|
+
elsif collections[k] && v.is_a?(Array)
|
|
97
|
+
coerce_collection(k, v)
|
|
98
|
+
else
|
|
99
|
+
v
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def coerce_implementation(k, v)
|
|
104
|
+
klass, * = (implementations[k] || {}).find do |_, options|
|
|
105
|
+
JSONAPIonify::Continuation.new(**options).check(v) { true }
|
|
106
|
+
end
|
|
107
|
+
return v unless klass && !v.is_a?(klass)
|
|
108
|
+
klass.new(v)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def coerce_collection(k, v)
|
|
112
|
+
klass, * = (collections[k] || {}).find do |_, options|
|
|
113
|
+
v.all? do |obj|
|
|
114
|
+
JSONAPIonify::Continuation.new(**options).check(obj) { true }
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
return v unless klass && !v.is_a?(klass)
|
|
118
|
+
klass.new(v)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
|
|
3
|
+
module JSONAPIonify::Structure
|
|
4
|
+
module Helpers
|
|
5
|
+
module ObjectSetters
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
# Setter
|
|
9
|
+
def []=(k, v)
|
|
10
|
+
raise TypeError, 'key must be a Symbol.' unless k.is_a? Symbol
|
|
11
|
+
object[k] = v
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Getter
|
|
15
|
+
def [](k)
|
|
16
|
+
raise TypeError, 'key must be a Symbol.' unless k.is_a? Symbol
|
|
17
|
+
object[k]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|