jsonapionify 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
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