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.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +29 -0
  3. data/.csslintrc +2 -0
  4. data/.gitignore +11 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +1171 -0
  7. data/.ruby-version +1 -0
  8. data/.travis.yml +10 -0
  9. data/CODE_OF_CONDUCT.md +13 -0
  10. data/Gemfile +4 -0
  11. data/Guardfile +14 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +43 -0
  14. data/Rakefile +34 -0
  15. data/TODO +13 -0
  16. data/bin/console +14 -0
  17. data/bin/setup +7 -0
  18. data/config.ru +15 -0
  19. data/fixtures/documentation.json +364 -0
  20. data/jsonapionify.gemspec +50 -0
  21. data/lib/core_ext/boolean.rb +3 -0
  22. data/lib/jsonapionify/api/action.rb +211 -0
  23. data/lib/jsonapionify/api/attribute.rb +67 -0
  24. data/lib/jsonapionify/api/base/app_builder.rb +33 -0
  25. data/lib/jsonapionify/api/base/class_methods.rb +73 -0
  26. data/lib/jsonapionify/api/base/delegation.rb +15 -0
  27. data/lib/jsonapionify/api/base/doc_helper.rb +47 -0
  28. data/lib/jsonapionify/api/base/reloader.rb +10 -0
  29. data/lib/jsonapionify/api/base/resource_definitions.rb +39 -0
  30. data/lib/jsonapionify/api/base.rb +25 -0
  31. data/lib/jsonapionify/api/context.rb +14 -0
  32. data/lib/jsonapionify/api/context_delegate.rb +42 -0
  33. data/lib/jsonapionify/api/errors.rb +6 -0
  34. data/lib/jsonapionify/api/errors_object.rb +66 -0
  35. data/lib/jsonapionify/api/header_options.rb +13 -0
  36. data/lib/jsonapionify/api/param_options.rb +46 -0
  37. data/lib/jsonapionify/api/relationship/blocks.rb +41 -0
  38. data/lib/jsonapionify/api/relationship/many.rb +61 -0
  39. data/lib/jsonapionify/api/relationship/one.rb +36 -0
  40. data/lib/jsonapionify/api/relationship.rb +89 -0
  41. data/lib/jsonapionify/api/resource/builders.rb +81 -0
  42. data/lib/jsonapionify/api/resource/class_methods.rb +82 -0
  43. data/lib/jsonapionify/api/resource/defaults/actions.rb +11 -0
  44. data/lib/jsonapionify/api/resource/defaults/errors.rb +99 -0
  45. data/lib/jsonapionify/api/resource/defaults/request_contexts.rb +96 -0
  46. data/lib/jsonapionify/api/resource/defaults/response_contexts.rb +31 -0
  47. data/lib/jsonapionify/api/resource/defaults.rb +10 -0
  48. data/lib/jsonapionify/api/resource/definitions/actions.rb +196 -0
  49. data/lib/jsonapionify/api/resource/definitions/attributes.rb +51 -0
  50. data/lib/jsonapionify/api/resource/definitions/contexts.rb +16 -0
  51. data/lib/jsonapionify/api/resource/definitions/helpers.rb +9 -0
  52. data/lib/jsonapionify/api/resource/definitions/pagination.rb +79 -0
  53. data/lib/jsonapionify/api/resource/definitions/params.rb +49 -0
  54. data/lib/jsonapionify/api/resource/definitions/relationships.rb +42 -0
  55. data/lib/jsonapionify/api/resource/definitions/request_headers.rb +103 -0
  56. data/lib/jsonapionify/api/resource/definitions/response_headers.rb +22 -0
  57. data/lib/jsonapionify/api/resource/definitions/scopes.rb +50 -0
  58. data/lib/jsonapionify/api/resource/definitions/sorting.rb +85 -0
  59. data/lib/jsonapionify/api/resource/definitions.rb +14 -0
  60. data/lib/jsonapionify/api/resource/error_handling.rb +108 -0
  61. data/lib/jsonapionify/api/resource/http.rb +11 -0
  62. data/lib/jsonapionify/api/resource/includer.rb +4 -0
  63. data/lib/jsonapionify/api/resource.rb +35 -0
  64. data/lib/jsonapionify/api/response.rb +47 -0
  65. data/lib/jsonapionify/api/server/mock_response.rb +37 -0
  66. data/lib/jsonapionify/api/server/request.rb +78 -0
  67. data/lib/jsonapionify/api/server.rb +50 -0
  68. data/lib/jsonapionify/api/test_helper.rb +52 -0
  69. data/lib/jsonapionify/api.rb +9 -0
  70. data/lib/jsonapionify/autoload.rb +52 -0
  71. data/lib/jsonapionify/callbacks.rb +49 -0
  72. data/lib/jsonapionify/character_range.rb +41 -0
  73. data/lib/jsonapionify/continuation.rb +26 -0
  74. data/lib/jsonapionify/documentation/template.erb +487 -0
  75. data/lib/jsonapionify/documentation.rb +40 -0
  76. data/lib/jsonapionify/enumerable_observer.rb +91 -0
  77. data/lib/jsonapionify/indented_string.rb +27 -0
  78. data/lib/jsonapionify/inherited_attributes.rb +125 -0
  79. data/lib/jsonapionify/structure/collections/base.rb +104 -0
  80. data/lib/jsonapionify/structure/collections/errors.rb +7 -0
  81. data/lib/jsonapionify/structure/collections/included_resources.rb +39 -0
  82. data/lib/jsonapionify/structure/collections/resource_identifiers.rb +7 -0
  83. data/lib/jsonapionify/structure/collections/resources.rb +7 -0
  84. data/lib/jsonapionify/structure/helpers/errors.rb +71 -0
  85. data/lib/jsonapionify/structure/helpers/inherits_origin.rb +17 -0
  86. data/lib/jsonapionify/structure/helpers/member_names.rb +37 -0
  87. data/lib/jsonapionify/structure/helpers/meta_delegate.rb +16 -0
  88. data/lib/jsonapionify/structure/helpers/object_defaults.rb +123 -0
  89. data/lib/jsonapionify/structure/helpers/object_setters.rb +21 -0
  90. data/lib/jsonapionify/structure/helpers/pagination_links.rb +10 -0
  91. data/lib/jsonapionify/structure/helpers/validations.rb +296 -0
  92. data/lib/jsonapionify/structure/maps/base.rb +25 -0
  93. data/lib/jsonapionify/structure/maps/error_links.rb +7 -0
  94. data/lib/jsonapionify/structure/maps/links.rb +21 -0
  95. data/lib/jsonapionify/structure/maps/relationship_links.rb +11 -0
  96. data/lib/jsonapionify/structure/maps/relationships.rb +23 -0
  97. data/lib/jsonapionify/structure/maps/resource_links.rb +7 -0
  98. data/lib/jsonapionify/structure/maps/top_level_links.rb +10 -0
  99. data/lib/jsonapionify/structure/objects/attributes.rb +29 -0
  100. data/lib/jsonapionify/structure/objects/base.rb +166 -0
  101. data/lib/jsonapionify/structure/objects/error.rb +16 -0
  102. data/lib/jsonapionify/structure/objects/included_resource.rb +14 -0
  103. data/lib/jsonapionify/structure/objects/jsonapi.rb +7 -0
  104. data/lib/jsonapionify/structure/objects/link.rb +18 -0
  105. data/lib/jsonapionify/structure/objects/meta.rb +7 -0
  106. data/lib/jsonapionify/structure/objects/relationship.rb +20 -0
  107. data/lib/jsonapionify/structure/objects/resource.rb +45 -0
  108. data/lib/jsonapionify/structure/objects/resource_identifier.rb +40 -0
  109. data/lib/jsonapionify/structure/objects/source.rb +10 -0
  110. data/lib/jsonapionify/structure/objects/top_level.rb +105 -0
  111. data/lib/jsonapionify/structure.rb +27 -0
  112. data/lib/jsonapionify/types/array_type.rb +32 -0
  113. data/lib/jsonapionify/types/boolean_type.rb +22 -0
  114. data/lib/jsonapionify/types/date_string_type.rb +28 -0
  115. data/lib/jsonapionify/types/float_type.rb +8 -0
  116. data/lib/jsonapionify/types/integer_type.rb +9 -0
  117. data/lib/jsonapionify/types/object_type.rb +22 -0
  118. data/lib/jsonapionify/types/string_type.rb +66 -0
  119. data/lib/jsonapionify/types/time_string_type.rb +28 -0
  120. data/lib/jsonapionify/types.rb +49 -0
  121. data/lib/jsonapionify/unstrict_proc.rb +28 -0
  122. data/lib/jsonapionify/version.rb +3 -0
  123. data/lib/jsonapionify.rb +37 -0
  124. metadata +530 -0
@@ -0,0 +1,85 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Definitions::Sorting
3
+
4
+ class SortField
5
+
6
+ attr_reader :name, :order
7
+
8
+ def initialize(name)
9
+ if name.to_s.start_with? '-'
10
+ @name = name.to_s[1..-1].to_sym
11
+ @order = :desc
12
+ else
13
+ @name = name
14
+ @order = :asc
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ STRATEGIES = {
21
+ active_record: proc { |collection, fields|
22
+ order_hash = fields.each_with_object({}) do |field, hash|
23
+ hash[field.name] = field.order
24
+ end
25
+ collection.order order_hash
26
+ },
27
+ enumerable: proc { |collection, fields|
28
+ fields.reverse.reduce(collection) do |o, field|
29
+ result = o.sort_by(&field.name)
30
+ case field.order
31
+ when :asc
32
+ result
33
+ when :desc
34
+ result.reverse
35
+ end
36
+ end
37
+ }
38
+ }
39
+ STRATEGIES[:array] = STRATEGIES[:enumerable]
40
+ DEFAULT = STRATEGIES[:enumerable]
41
+
42
+ def self.extended(klass)
43
+ klass.class_eval do
44
+
45
+ context(:sort_params, readonly: true) do |context|
46
+ should_error = false
47
+ fields = context.params['sort'].to_s.split(',')
48
+ fields.each_with_object([]) do |field, array|
49
+ field, resource = field.split('.').map(&:to_sym).reverse
50
+ if self.class <= self.class.api.resource(resource || self.class.type)
51
+ if self.class.field_valid? field
52
+ array << SortField.new(field)
53
+ else
54
+ should_error = true
55
+ error :sort_parameter_invalid do
56
+ detail "resource `#{self.class.type}` does not have field: #{field}"
57
+ end
58
+ end
59
+ end
60
+ end.tap do
61
+ raise error_exception if should_error
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def sorting(strategy = nil, &block)
68
+ param :sort
69
+ context :sorted_collection do |context|
70
+ unless (actual_block = block)
71
+ actual_strategy = strategy || self.class.default_strategy
72
+ actual_block = actual_strategy ? STRATEGIES[actual_strategy] : DEFAULT
73
+ end
74
+ Object.new.instance_exec(context.collection, context.sort_params, &actual_block)
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def default_sort
81
+ api.resource_definitions.keys.each_with_object({}) { |type, h| h[type] = [] }
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,14 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Definitions
3
+ extend JSONAPIonify::Autoload
4
+ autoload_all
5
+
6
+ def self.extended(klass)
7
+ klass.extend Contexts
8
+ constants(false).each do |const|
9
+ mod = const_get(const, false)
10
+ klass.extend mod unless klass.singleton_class < mod
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,108 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::ErrorHandling
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include ActiveSupport::Rescuable
7
+ context(:error_exception) { Class.new(StandardError) }
8
+ context(:errors, readonly: true) do
9
+ ErrorsObject.new
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ def error(name, &block)
16
+ self.error_definitions = self.error_definitions.merge name.to_sym => block
17
+ end
18
+
19
+ def rescue_from(*klasses, error:, &block)
20
+ super(*klasses) do |exception|
21
+ errors.evaluate(
22
+ error_block: lookup_error(error),
23
+ runtime_block: block || proc {},
24
+ backtrace: exception.backtrace
25
+ )
26
+ end
27
+ end
28
+
29
+ def error_definitions=(hash)
30
+ @error_definitions = hash
31
+ end
32
+
33
+ def error_definitions
34
+ @error_definitions ||= {}
35
+ if superclass.respond_to?(:error_definitions)
36
+ superclass.error_definitions.merge(@error_definitions)
37
+ else
38
+ @error_definitions
39
+ end
40
+ end
41
+ end
42
+
43
+ def error(name, *args, &block)
44
+ errors.evaluate(
45
+ *args,
46
+ error_block: lookup_error(name),
47
+ runtime_block: block
48
+ )
49
+ end
50
+
51
+ def error_now(name, *args, &block)
52
+ error(name, *args, &block)
53
+ raise error_exception
54
+ end
55
+
56
+ def set_errors(collection)
57
+ errors.set collection
58
+ end
59
+
60
+ def error_meta
61
+ errors.meta
62
+ end
63
+
64
+ private
65
+
66
+ def lookup_error(name)
67
+ self.class.error_definitions[name].tap do |error|
68
+ raise ArgumentError, "Error does not exist: #{name}" unless error
69
+ end
70
+ end
71
+
72
+ def rescued_response(exception)
73
+ rescue_with_handler(exception) || begin
74
+ errors.evaluate(
75
+ error_block: lookup_error(:internal_server_error),
76
+ runtime_block: proc {
77
+ unless ENV['RACK_ENV'] == 'production'
78
+ detail exception.message
79
+ meta[:error_class] = exception.class.name
80
+ end
81
+ },
82
+ backtrace: exception.backtrace
83
+ )
84
+ end
85
+ ensure
86
+ return error_response
87
+ end
88
+
89
+ def error_response
90
+ Rack::Response.new.tap do |response|
91
+ error_collection = errors.collection
92
+ status_codes = error_collection.map { |error| error[:status] }.compact.uniq.sort
93
+ response.status =
94
+ if status_codes.length == 1
95
+ status_codes[0].to_i
96
+ elsif status_codes.blank?
97
+ 500
98
+ else
99
+ (status_codes.last[0] + "00").to_i
100
+ end
101
+ response_headers.each { |k, v| response.headers[k] = v }
102
+ response.headers['content-type'] = 'application/vnd.api+json'
103
+ response.write(errors.top_level.to_json)
104
+ end.finish
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,11 @@
1
+ module JSONAPIonify::Api
2
+ class Resource::Http < Resource
3
+
4
+ Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |symbol, code|
5
+ define_action(symbol).response status: code do
6
+ error_now symbol
7
+ end
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Includer
3
+ end
4
+ end
@@ -0,0 +1,35 @@
1
+ require 'active_support/rescuable'
2
+ require 'rack/response'
3
+ require 'active_support/json'
4
+
5
+ module JSONAPIonify::Api
6
+ class Resource
7
+ extend JSONAPIonify::Autoload
8
+ autoload_all
9
+
10
+ extend Definitions
11
+ extend ClassMethods
12
+
13
+ include ErrorHandling
14
+ include Builders
15
+ include Defaults
16
+
17
+ def self.inherited(subclass)
18
+ super(subclass)
19
+ subclass.class_eval do
20
+ context(:api, readonly: true) { api }
21
+ context(:resource, readonly: true) { self }
22
+ end
23
+ end
24
+
25
+ def self.example_instance(id=1)
26
+ OpenStruct.new.tap do |instance|
27
+ instance.send "#{id_attribute}=", (id).to_s
28
+ attributes.select(&:read?).each do |attribute|
29
+ instance.send "#{attribute.name}=", attribute.example
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ module JSONAPIonify::Api
2
+ class Response
3
+ attr_reader :action, :accept, :response_block, :status
4
+
5
+ def initialize(action, accept: nil, status: nil, &block)
6
+ @action = action
7
+ @response_block = block || proc {}
8
+ @accept = accept || 'application/vnd.api+json'
9
+ @status = status || 200
10
+ end
11
+
12
+ def ==(other)
13
+ self.class == other.class &&
14
+ %i{@accept}.all? do |ivar|
15
+ instance_variable_get(ivar) == other.instance_variable_get(ivar)
16
+ end
17
+ end
18
+
19
+ def accept?(request)
20
+ request.accept.any? do |accept|
21
+ @accept == accept || accept == '*/*' || self.accept == '*/*'
22
+ end
23
+ end
24
+
25
+ def documentation_object
26
+ OpenStruct.new(
27
+ accept: accept,
28
+ content_type: accept,
29
+ status: status
30
+ )
31
+ end
32
+
33
+ def call(instance, context)
34
+ response = self
35
+ instance.instance_eval do
36
+ body = instance_exec(context, &response.response_block)
37
+ Rack::Response.new.tap do |rack_response|
38
+ rack_response.status = response.status
39
+ response_headers.each { |k, v| rack_response.headers[k] = v }
40
+ rack_response.headers['content-type'] = response.accept unless response.accept == '*/*'
41
+ rack_response.write(body) unless body.nil?
42
+ end.finish
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,37 @@
1
+ require 'rack/utils'
2
+
3
+ module JSONAPIonify::Api
4
+ class Server::MockResponse
5
+ attr_reader :status, :headers, :body
6
+
7
+ def initialize(status, headers, body)
8
+ @status = status
9
+ @body = body.is_a?(Rack::BodyProxy) ? body.body : body
10
+ @headers = Rack::Utils::HeaderHash.new headers
11
+ end
12
+
13
+ def body
14
+ return nil unless @body.present?
15
+ JSON.pretty_generate(Oj.load(@body.join("\n")))
16
+ rescue Oj::ParseError
17
+ @body
18
+ end
19
+
20
+ def http_string
21
+ # HTTP/1.1 200 OK
22
+ # Date: Fri, 31 Dec 1999 23:59:59 GMT
23
+ # Content-Type: text/html
24
+ # Content-Length: 1354
25
+ #
26
+ # <body>
27
+ [].tap do |lines|
28
+ lines << "HTTP/1.1 #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}"
29
+ headers.each do |k, v|
30
+ lines << "#{k.split('-').map(&:capitalize).join('-')}: #{v}"
31
+ end
32
+ lines << ''
33
+ lines << body if body.present?
34
+ end.join("\n")
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,78 @@
1
+ require 'rack/request'
2
+ require 'rack/utils'
3
+ require 'rack/mock'
4
+
5
+ module JSONAPIonify::Api
6
+ class Server::Request < Rack::Request
7
+ def self.env_for(url, method, options = {})
8
+ options[:method] = method
9
+ new Rack::MockRequest.env_for(url, options)
10
+ end
11
+
12
+ def headers
13
+ Rack::Utils::HeaderHash.new(
14
+ env.select do |name, _|
15
+ name.start_with?('HTTP_') && !%w{HTTP_VERSION}.include?(name)
16
+ end.each_with_object({}) do |(name, value), hash|
17
+ hash[name[5..-1].gsub('_', '-')] = value
18
+ end
19
+ )
20
+ end
21
+
22
+ def http_string
23
+ # GET /path/file.html HTTP/1.0
24
+ # From: someuser@jmarshall.com
25
+ # User-Agent: HTTPTool/1.0
26
+ # [blank line here]
27
+ [].tap do |lines|
28
+ lines << "#{request_method} #{fullpath} HTTP/1.1"
29
+ headers.each do |k, v|
30
+ lines << "#{k.split('-').map(&:capitalize).join('-')}: #{v}"
31
+ end
32
+ lines << ''
33
+ lines << pretty_body if has_body?
34
+ end.join("\n")
35
+ end
36
+
37
+ def pretty_body
38
+ return '' unless has_body?
39
+ value = body.read
40
+ JSON.pretty_generate(Oj.load(value))
41
+ rescue Oj::ParseError
42
+ value
43
+ ensure
44
+ body.rewind
45
+ end
46
+
47
+ def accept
48
+ accepts = (headers['accept'] || '*/*').split(',')
49
+ accepts.to_a.sort_by! do |accept|
50
+ _, *media_type_params = accept.split(';')
51
+ rqf = media_type_params.find { |mtp| mtp.start_with? 'q=' }
52
+ -(rqf ? rqf[2..-1].to_f : 1.0)
53
+ end.map do |accept|
54
+ mime, *media_type_params = accept.split(';')
55
+ media_type_params.reject! { |mtp| mtp.start_with? 'q=' }
56
+ [mime, *media_type_params].join(';')
57
+ end
58
+ end
59
+
60
+ def authorizations
61
+ parts = headers['authorization'].to_s.split(' ')
62
+ parts.length == 2 ? Rack::Utils::HeaderHash.new([parts].to_h) : {}
63
+ end
64
+
65
+ def has_body?
66
+ body.read(1).present?
67
+ ensure
68
+ body.rewind
69
+ end
70
+
71
+ def root_url
72
+ URI.parse(url).tap do |uri|
73
+ uri.query = nil
74
+ uri.path.chomp! path_info
75
+ end.to_s
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,50 @@
1
+ require 'delegate'
2
+ require 'active_support/core_ext/object/blank'
3
+
4
+ module JSONAPIonify::Api
5
+ class Server
6
+ extend JSONAPIonify::Autoload
7
+ autoload_all
8
+
9
+ attr_reader :api
10
+
11
+ def initialize(api)
12
+ @api = api
13
+ end
14
+
15
+ def call(env)
16
+ Processor.new(env, api).response
17
+ end
18
+
19
+ class Processor
20
+ attr_reader :request, :id, :more, :api
21
+
22
+ def initialize(env, api)
23
+ @api = api
24
+ @request = Request.new(env)
25
+ request.path_info.split('/').tap(&:shift).tap do |parts|
26
+ @resource, @id, @relationship, @relationship_name, *@more = parts
27
+ request.env['jsonapionify.resource_name'] = @resource if @resource
28
+ request.env['jsonapionify.resource'] = resource if @resource
29
+ request.env['jsonapionify.id'] = @id if @id
30
+ end
31
+ end
32
+
33
+ def response
34
+ @resource ? resource.process(request) : api_index
35
+ rescue Errors::ResourceNotFound
36
+ Resource::Http.process(:not_found, request)
37
+ end
38
+
39
+ private
40
+
41
+ def api_index
42
+ api.process_index(request)
43
+ end
44
+
45
+ def resource
46
+ api.resource(@resource)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,52 @@
1
+ require 'rack/test'
2
+ require 'active_support/concern'
3
+
4
+ module JSONAPIonify
5
+ module Api::TestHelper
6
+ extend ActiveSupport::Concern
7
+ include Rack::Test::Methods
8
+
9
+ module ClassMethods
10
+ def set_api(api)
11
+ define_method(:app) do
12
+ api
13
+ end
14
+ end
15
+ end
16
+
17
+ def set_headers
18
+ @set_headers ||= Rack::Utils::HeaderHash.new
19
+ end
20
+
21
+ def json(hash)
22
+ Oj.dump hash.deep_stringify_keys
23
+ end
24
+
25
+ def last_response_json
26
+ Oj.load last_response.body
27
+ end
28
+
29
+ def header(name, value)
30
+ set_headers[name] = value
31
+ super
32
+ end
33
+
34
+ def content_type(value)
35
+ header('content-type', value)
36
+ end
37
+
38
+ def accept(*values)
39
+ header('accept', values.join(','))
40
+ end
41
+
42
+ def delete(*args, &block)
43
+ header('content-type', set_headers['content-type'].to_s)
44
+ super
45
+ end
46
+
47
+ def authorization(type, value)
48
+ header 'Authorization', [type, value].join(' ')
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,9 @@
1
+ module JSONAPIonify::Api
2
+ extend JSONAPIonify::Autoload
3
+ autoload_all
4
+
5
+ module Actions
6
+ extend JSONAPIonify::Autoload
7
+ autoload_all 'api/actions'
8
+ end
9
+ end
@@ -0,0 +1,52 @@
1
+ require 'active_support/inflections'
2
+
3
+ module JSONAPIonify
4
+ module Autoload
5
+ def self.eager_load!
6
+ load_tracker = {}
7
+ while unloaded.present?
8
+ unloaded.each do |mod, consts|
9
+ consts.each do |const|
10
+ tracker = [mod.name, const].join('::')
11
+ begin
12
+ mod.const_get(const, false)
13
+ rescue NameError => e
14
+ load_tracker[tracker] = load_tracker[tracker].to_i + 1
15
+ if !e.message.include?('uninitialized constant') || load_tracker[tracker] > 50
16
+ raise e
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def self.unloaded
25
+ modules = ObjectSpace.each_object.select do |o|
26
+ o.is_a?(Module)
27
+ end
28
+ modules.each_with_object({}) do |mod, hash|
29
+ autoloadable_constants = mod.constants.each_with_object([]) do |const, ary|
30
+ if mod.autoload?(const) && mod.autoload?(const).include?(__dir__)
31
+ ary << const
32
+ end
33
+ end
34
+ if autoloadable_constants.present?
35
+ hash[mod] = autoloadable_constants
36
+ end
37
+ end
38
+ end
39
+
40
+ def autoload_all(dir=nil)
41
+ file = caller[0].split(/\:\d/)[0]
42
+ base_dir = File.expand_path File.dirname(file)
43
+ dir ||= name.split('::').last.underscore
44
+ Dir.glob("#{base_dir}/#{dir}/*.rb").each do |file|
45
+ basename = File.basename file, File.extname(file)
46
+ fullpath = File.expand_path file
47
+ const_name = basename.camelize.to_sym
48
+ autoload const_name, fullpath
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,49 @@
1
+ require 'active_support/concern'
2
+
3
+ module JSONAPIonify
4
+ module Callbacks
5
+ extend ActiveSupport::Concern
6
+ included do
7
+
8
+ def self.define_callbacks(*names)
9
+ names.each do |name|
10
+ chains = {
11
+ main: "__#{name}_callback_chain",
12
+ before: "__#{name}_before_callback_chain",
13
+ after: "__#{name}_after_callback_chain"
14
+ }
15
+ define_method chains[:main] do |*args, &block|
16
+ if send(chains[:before], *args) != false
17
+ value = instance_exec(*args, &block)
18
+ value if send(chains[:after], *args) != false
19
+ end
20
+ end
21
+
22
+ # Define before and after chains
23
+ %i{after before}.each do |timing|
24
+ define_method chains[timing] do |*|
25
+ end
26
+
27
+ define_singleton_method "#{timing}_#{name}" do |sym = nil, &outer_block|
28
+ outer_block = (outer_block || sym).to_proc
29
+ prev_chain = instance_method(chains[timing])
30
+ define_method chains[timing] do |*args, &block|
31
+ if prev_chain.bind(self).call(*args, &block) != false
32
+ instance_exec(*args, &outer_block)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ private *chains.values
39
+
40
+ end
41
+ end
42
+ end
43
+
44
+ def run_callbacks(name, *args, &block)
45
+ send("__#{name}_callback_chain", *args, &block)
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ module JSONAPIonify
2
+ class CharacterRange
3
+ include Enumerable
4
+
5
+ class << self
6
+ def [](*args)
7
+ new(*args)
8
+ end
9
+ end
10
+
11
+ def initialize(start_char, end_char)
12
+ if [start_char, end_char].any? { |c| c.length > 1 }
13
+ raise ArgumentError, 'must be single characters'
14
+ end
15
+ @start_char = start_char
16
+ @end_char = end_char
17
+ end
18
+
19
+ def each
20
+ range.each do |ord|
21
+ begin
22
+ char = ord.chr(Encoding::UTF_8)
23
+ yield char
24
+ rescue RangeError
25
+ next
26
+ end
27
+ end
28
+ end
29
+
30
+ def to_a
31
+ each.to_a
32
+ end
33
+
34
+ private
35
+
36
+ def range
37
+ (@start_char.ord)..(@end_char.ord)
38
+ end
39
+
40
+ end
41
+ end