axel 0.0.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.
Files changed (128) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +21 -0
  3. data/.octopolo.yml +2 -0
  4. data/.rspec +2 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +23 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +271 -0
  11. data/Rakefile +13 -0
  12. data/app/models/axel/api_proxy.rb +86 -0
  13. data/app/models/axel/associations/base.rb +64 -0
  14. data/app/models/axel/associations/belongs_to.rb +43 -0
  15. data/app/models/axel/associations/has_many.rb +29 -0
  16. data/app/models/axel/associations/has_one.rb +30 -0
  17. data/app/models/axel/payload.rb +4 -0
  18. data/app/models/axel/payload/base.rb +107 -0
  19. data/app/models/axel/payload/errors.rb +62 -0
  20. data/app/models/axel/payload/metadata.rb +57 -0
  21. data/app/models/axel/querier.rb +166 -0
  22. data/app/models/axel/router.rb +119 -0
  23. data/app/models/axel/service_resource.rb +4 -0
  24. data/app/models/axel/service_resource/associations.rb +80 -0
  25. data/app/models/axel/service_resource/attributes.rb +23 -0
  26. data/app/models/axel/service_resource/automatic_resource.rb +23 -0
  27. data/app/models/axel/service_resource/base.rb +47 -0
  28. data/app/models/axel/service_resource/builder.rb +40 -0
  29. data/app/models/axel/service_resource/inspects.rb +17 -0
  30. data/app/models/axel/service_resource/payload_parser.rb +46 -0
  31. data/app/models/axel/service_resource/queries.rb +25 -0
  32. data/app/models/axel/service_resource/requesters.rb +49 -0
  33. data/app/models/axel/service_resource/routes.rb +19 -0
  34. data/app/models/axel/service_resource/typhoid_extensions.rb +134 -0
  35. data/app/views/axel/base/empty.json.erb +0 -0
  36. data/app/views/axel/base/empty.xml.builder +0 -0
  37. data/app/views/layouts/axel.json.jbuilder +7 -0
  38. data/app/views/layouts/axel.xml.builder +12 -0
  39. data/axel.gemspec +42 -0
  40. data/lib/axel.rb +56 -0
  41. data/lib/axel/application_extensions.rb +13 -0
  42. data/lib/axel/application_helper.rb +27 -0
  43. data/lib/axel/base_controller.rb +6 -0
  44. data/lib/axel/cascadable_attribute.rb +33 -0
  45. data/lib/axel/configurations/resource.rb +29 -0
  46. data/lib/axel/configurations/service.rb +28 -0
  47. data/lib/axel/configurator.rb +54 -0
  48. data/lib/axel/configurators/services.rb +29 -0
  49. data/lib/axel/controller_base.rb +27 -0
  50. data/lib/axel/controller_helpers.rb +209 -0
  51. data/lib/axel/controller_parameters.rb +32 -0
  52. data/lib/axel/engine.rb +14 -0
  53. data/lib/axel/inspector.rb +91 -0
  54. data/lib/axel/payload/remote_error.rb +14 -0
  55. data/lib/axel/request_options.rb +26 -0
  56. data/lib/axel/uri.rb +47 -0
  57. data/lib/axel/version.rb +3 -0
  58. data/lib/generators/axel/install_generator.rb +16 -0
  59. data/lib/generators/templates/README.md +22 -0
  60. data/lib/generators/templates/axel.rb +81 -0
  61. data/script/rails +5 -0
  62. data/spec/controllers/pages_controller_spec.rb +217 -0
  63. data/spec/dummy/README.rdoc +261 -0
  64. data/spec/dummy/Rakefile +7 -0
  65. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  66. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  67. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  68. data/spec/dummy/app/controllers/pages_controller.rb +6 -0
  69. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  70. data/spec/dummy/app/mailers/.gitkeep +0 -0
  71. data/spec/dummy/app/models/.gitkeep +0 -0
  72. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  73. data/spec/dummy/config.ru +4 -0
  74. data/spec/dummy/config/application.rb +62 -0
  75. data/spec/dummy/config/boot.rb +10 -0
  76. data/spec/dummy/config/database.yml +25 -0
  77. data/spec/dummy/config/environment.rb +5 -0
  78. data/spec/dummy/config/environments/development.rb +37 -0
  79. data/spec/dummy/config/environments/production.rb +67 -0
  80. data/spec/dummy/config/environments/test.rb +37 -0
  81. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  82. data/spec/dummy/config/initializers/inflections.rb +15 -0
  83. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  84. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  85. data/spec/dummy/config/initializers/session_store.rb +8 -0
  86. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  87. data/spec/dummy/config/locales/en.yml +5 -0
  88. data/spec/dummy/config/routes.rb +3 -0
  89. data/spec/dummy/db/development.sqlite3 +0 -0
  90. data/spec/dummy/db/test.sqlite3 +0 -0
  91. data/spec/dummy/lib/assets/.gitkeep +0 -0
  92. data/spec/dummy/log/.gitignore +1 -0
  93. data/spec/dummy/log/.gitkeep +0 -0
  94. data/spec/dummy/public/404.html +26 -0
  95. data/spec/dummy/public/422.html +26 -0
  96. data/spec/dummy/public/500.html +25 -0
  97. data/spec/dummy/public/favicon.ico +0 -0
  98. data/spec/dummy/script/rails +6 -0
  99. data/spec/envelope_integration_check.rb +96 -0
  100. data/spec/helpers/axel/application_helper_spec.rb +31 -0
  101. data/spec/lib/axel/associations/base_spec.rb +28 -0
  102. data/spec/lib/axel/associations/belongs_to_spec.rb +62 -0
  103. data/spec/lib/axel/associations/has_many_spec.rb +23 -0
  104. data/spec/lib/axel/associations/has_one_spec.rb +23 -0
  105. data/spec/lib/axel/configurations/resource_spec.rb +15 -0
  106. data/spec/lib/axel/configurations/service_spec.rb +31 -0
  107. data/spec/lib/axel/configurator_spec.rb +26 -0
  108. data/spec/lib/axel/configurators/services_spec.rb +37 -0
  109. data/spec/lib/axel/controller_base_spec.rb +16 -0
  110. data/spec/lib/axel/controller_parameters_spec.rb +31 -0
  111. data/spec/lib/axel/inspector_spec.rb +45 -0
  112. data/spec/lib/axel/request_options_spec.rb +50 -0
  113. data/spec/lib/axel/uri_spec.rb +42 -0
  114. data/spec/lib/axel_spec.rb +64 -0
  115. data/spec/models/axel/api_proxy_spec.rb +66 -0
  116. data/spec/models/axel/payload/errors_spec.rb +165 -0
  117. data/spec/models/axel/payload/metadata_spec.rb +141 -0
  118. data/spec/models/axel/querier_spec.rb +158 -0
  119. data/spec/models/axel/router_spec.rb +115 -0
  120. data/spec/models/axel/service_resource/base_spec.rb +244 -0
  121. data/spec/models/axel/service_resource/builder_spec.rb +64 -0
  122. data/spec/models/axel/service_resource/payload_parser_spec.rb +60 -0
  123. data/spec/spec_helper.rb +39 -0
  124. data/spec/support/address.rb +5 -0
  125. data/spec/support/persona.rb +15 -0
  126. data/spec/support/user.rb +6 -0
  127. data/spec/views/axel/base/empty_spec.rb +34 -0
  128. metadata +508 -0
@@ -0,0 +1,119 @@
1
+ module Axel
2
+ class Router
3
+ private
4
+ attr_writer :path
5
+ attr_writer :origin_klass
6
+ attr_writer :method_name
7
+ attr_writer :parameters
8
+ attr_writer :options
9
+
10
+ public
11
+ attr_reader :path
12
+ attr_reader :origin_klass
13
+ attr_reader :method_name
14
+ attr_reader :parameters
15
+ attr_reader :options
16
+
17
+ def initialize(klass, path, name, options = {})
18
+ self.path = path.to_s
19
+ self.origin_klass = klass
20
+ self.method_name = name.to_s
21
+ self.options = options || {}
22
+ extract_parameters!
23
+ end
24
+
25
+ def define_route
26
+ setup_klass_method
27
+ self
28
+ end
29
+
30
+ def route(*args)
31
+ route_path = build_path_with_args *args
32
+ route_options = extract_routed_args_options args
33
+ run_route route_path, route_options
34
+ end
35
+
36
+ # The class constant used for querying and dumping the
37
+ # response to
38
+ def klass
39
+ klass_from_class_option || klass_from_class_name_option || origin_klass
40
+ end
41
+ private :klass
42
+
43
+ def klass_from_class_option(class_option = options[:class])
44
+ class_option.respond_to?(:querier) && class_option
45
+ end
46
+ private :klass_from_class_option
47
+
48
+ def klass_from_class_name_option
49
+ options[:class_name].present? &&
50
+ klass_from_class_option(options[:class_name].to_s.camelize.safe_constantize)
51
+ end
52
+ private :klass_from_class_name_option
53
+
54
+ def run_route(route_path, route_options)
55
+ klass.querier.without_default_path.at_path(route_path).request_options route_options
56
+ end
57
+ private :run_route
58
+
59
+ def extract_routed_args_options(args)
60
+ hash_options = arity > 0 && args.first.is_a?(Hash)
61
+ first_option = hash_options ? 1 : arity - 1
62
+ args[first_option..-1].to_a.extract_options!
63
+ end
64
+ private :extract_routed_args_options
65
+
66
+ # Sets up a new static method on the class that defines the
67
+ # route. The method actually delegates to our router with the
68
+ # method's name.
69
+ def setup_klass_method
70
+ origin_klass.define_singleton_method method_name do |*args|
71
+ self.routes[__method__].route *args
72
+ end
73
+ end
74
+ private :setup_klass_method
75
+
76
+ def extract_parameters!
77
+ if parameters.nil?
78
+ self.parameters = path.
79
+ split("/").
80
+ select { |s| s.start_with?(":") }.
81
+ map { |s| s[1..-1] }
82
+ else
83
+ true
84
+ end
85
+ end
86
+ private :extract_parameters!
87
+
88
+ def arity
89
+ parameters.count
90
+ end
91
+ private :arity
92
+
93
+ def build_path_with_args(*args)
94
+ param_options = args.first.is_a?(Hash) ? handle_hash(args.first) : handle_splat(args)
95
+ path.split("/").collect { |piece|
96
+ piece.match(/^:/) ? param_options[piece.to_s[1..-1]] : piece
97
+ }.join "/"
98
+ end
99
+ private :build_path_with_args
100
+
101
+ def handle_hash(hash)
102
+ hash = hash.with_indifferent_access
103
+ parameters.collect { |param_name| { param_name => hash[param_name] } }.
104
+ inject({}) { |new_hash, found| new_hash.merge found }.
105
+ with_indifferent_access
106
+ end
107
+ private :handle_hash
108
+
109
+ def handle_splat(splat)
110
+ values = splat[0...arity].map(&:to_s)
111
+ while values.size < arity do
112
+ values << ""
113
+ end
114
+ Hash[parameters.zip(values)].
115
+ with_indifferent_access
116
+ end
117
+ private :handle_splat
118
+ end
119
+ end
@@ -0,0 +1,4 @@
1
+ module Axel
2
+ module ServiceResource
3
+ end
4
+ end
@@ -0,0 +1,80 @@
1
+ module Axel
2
+ module ServiceResource
3
+ module Associations
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ def method_missing(method_name, *args, &block)
9
+ if self.class.associations_respond_to?(method_name)
10
+ cache_association method_name do
11
+ self.class.association(method_name).run_method(self, method_name, *args, &block)
12
+ end
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def respond_to?(method_name, include_private = false)
19
+ self.class.associations_respond_to?(method_name) || super
20
+ end
21
+
22
+ def reset_association_cache!
23
+ @association_cache = {}
24
+ end
25
+
26
+ def association_cache
27
+ @assocation_cache ||= {}
28
+ end
29
+ private :association_cache
30
+
31
+ def cache_association(name, &block)
32
+ association_cache[name] ||= block.call
33
+ end
34
+ private :cache_association
35
+
36
+ module ClassMethods
37
+ def belongs_to(relation_name, options = {})
38
+ belongs_to_associations[relation_name] =
39
+ Axel::Associations::BelongsTo.new(self, relation_name, options)
40
+ end
41
+
42
+ def has_many(relation_name, options = {})
43
+ has_many_associations[relation_name] =
44
+ Axel::Associations::HasMany.new(self, relation_name, options)
45
+ end
46
+
47
+ def has_one(relation_name, options = {})
48
+ has_one_associations[relation_name] =
49
+ Axel::Associations::HasOne.new(self, relation_name, options)
50
+ end
51
+
52
+ def associations_respond_to?(method_name)
53
+ !!association(method_name)
54
+ end
55
+
56
+ def association(method_name)
57
+ associations.values.find { |association| association.handles_method? method_name }
58
+ end
59
+
60
+ private
61
+
62
+ def associations
63
+ belongs_to_associations.merge(has_many_associations).merge(has_one_associations)
64
+ end
65
+
66
+ def has_one_associations
67
+ @__has_one_associations ||= {}.with_indifferent_access
68
+ end
69
+
70
+ def belongs_to_associations
71
+ @__belongs_to_associations ||= {}.with_indifferent_access
72
+ end
73
+
74
+ def has_many_associations
75
+ @__has_many_associations ||= {}.with_indifferent_access
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,23 @@
1
+ module Axel
2
+ module ServiceResource
3
+ UnknownAttributeError = Class.new(StandardError)
4
+ module Attributes
5
+ def assign_attributes(new_attributes)
6
+ return unless new_attributes
7
+
8
+ new_attributes.each do |k, v|
9
+ if respond_to?("#{k}=")
10
+ send("#{k}=", v)
11
+ else
12
+ raise(UnknownAttributeError, "unknown attribute: #{k}")
13
+ end
14
+ end
15
+ end
16
+
17
+ def update_attributes(attributes)
18
+ assign_attributes(attributes)
19
+ save
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Axel
2
+ module ServiceResource
3
+ module AutomaticResource
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ # Name the resource. Used if you call your class Foo but the API
8
+ # Proxy know it as "Bar". This will resolve that configuration
9
+ # disconnect
10
+ def resource_name(name = nil)
11
+ @_resource_name = name.to_s.underscore.pluralize if name
12
+ @_resource_name || self.name.split("::").last.underscore.pluralize
13
+ end
14
+
15
+ # Resource object that holds configuration information about the service
16
+ # and path
17
+ def resource
18
+ Axel.resources[resource_name]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,47 @@
1
+ module Axel
2
+ module ServiceResource
3
+ class Base < Typhoid::Resource
4
+ extend Axel::CascadableAttribute
5
+ include Inspects
6
+ include Queries
7
+ include Routes
8
+ include AutomaticResource
9
+ include TyphoidExtensions
10
+ include Requesters
11
+ include Associations
12
+ include Attributes
13
+
14
+ # Standard fields that should be on almost all objects
15
+ # coming in
16
+ field :id
17
+ field :uri
18
+
19
+ delegate :paged?,
20
+ :total_pages,
21
+ to: :metadata
22
+
23
+ def initialize(params = {})
24
+ super (params || {}).with_indifferent_access
25
+ end
26
+
27
+ # Grab the classlevel block that defines parameters to pass with
28
+ # a reload and evaluate that within this instance
29
+ def reload_params
30
+ {}
31
+ end
32
+ private :reload_params
33
+
34
+ def reload
35
+ reset_association_cache!
36
+ request_and_load do
37
+ self.class.manual_request :get, reload_uri, reload_params
38
+ end
39
+ end
40
+
41
+ def reload_uri
42
+ request_uri
43
+ end
44
+ private :reload_uri
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,40 @@
1
+ module Axel
2
+ module ServiceResource
3
+ class Builder < Typhoid::Builder
4
+ def result
5
+ enveloped? ? parsed_body["result"] : parsed_body
6
+ end
7
+
8
+ def enveloped?
9
+ parsed_body.respond_to?("has_key?") && parsed_body.has_key?("metadata") && parsed_body.has_key?("result")
10
+ end
11
+
12
+ def array?
13
+ result.is_a?(Array)
14
+ end
15
+
16
+ def singular?
17
+ !array?
18
+ end
19
+
20
+ def metadata
21
+ parsed_body["metadata"] if enveloped?
22
+ end
23
+
24
+ def errors
25
+ parsed_body["errors"] if enveloped?
26
+ end
27
+
28
+ def compiled_payloads
29
+ Array(result).map { |res| { "metadata" => metadata, "errors" => errors, "result" => res } }
30
+ end
31
+
32
+ def build_array
33
+ compiled_payloads.collect { |single|
34
+ build_from_klass(single)
35
+ }
36
+ end
37
+ private :build_array
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,17 @@
1
+ module Axel
2
+ module ServiceResource
3
+ module Inspects
4
+ extend ActiveSupport::Concern
5
+
6
+ def inspect
7
+ Inspector.new(self, [:request_uri], [:attributes, :metadata, :remote_errors]).inspect
8
+ end
9
+
10
+ module ClassMethods
11
+ def inspect
12
+ Inspector.new(self, [:request_uri]).inspect
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ module Axel
2
+ module ServiceResource
3
+ class PayloadParser
4
+ def initialize(payload)
5
+ self.payload = ActiveSupport::HashWithIndifferentAccess.new payload
6
+ end
7
+
8
+ def parsed
9
+ [payload, result, metadata, remote_errors]
10
+ end
11
+
12
+ private
13
+ attr_accessor :payload
14
+
15
+ def metadata
16
+ Payload::Metadata.new bare_metadata
17
+ end
18
+
19
+ def remote_errors
20
+ Payload::Errors.new bare_remote_errors
21
+ end
22
+
23
+ def result
24
+ (payload[:result] || backup_result ).with_indifferent_access
25
+ end
26
+
27
+ def bare_metadata
28
+ (payload[:metadata] || {} ).with_indifferent_access
29
+ end
30
+
31
+ def bare_remote_errors
32
+ raw = payload[:errors] || payload[:error] || {}
33
+ raw = { messages: raw } if raw.is_a?(Array)
34
+ raw.with_indifferent_access
35
+ end
36
+
37
+ def payload_is_enveloped?
38
+ payload.is_a?(Hash) && payload.has_key?(:metadata) && payload.has_key?(:result)
39
+ end
40
+
41
+ def backup_result
42
+ payload_is_enveloped? ? {} : payload
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ Axel::Querier # Autoloading
2
+ module Axel
3
+ module ServiceResource
4
+ module Queries
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # Use Query methods from a Querier instance
9
+ class << self
10
+ delegate *Axel::Querier.query_methods, to: :querier
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+ # Entry point to querying methods like:
16
+ # #where
17
+ # #path
18
+ # #uri
19
+ def querier
20
+ Axel::Querier.new self
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ module Axel
2
+ module ServiceResource
3
+ module Requesters
4
+ extend ActiveSupport::Concern
5
+
6
+ def default_request_options;end
7
+
8
+ def retrieve_default_request_options(options)
9
+ RequestOptions.new(compiled_default_request_options, options).compiled
10
+ end
11
+ private :retrieve_default_request_options
12
+
13
+ def compiled_default_request_options
14
+ (self.class.default_request_options ||{}).merge(default_request_options || {})
15
+ end
16
+ private :compiled_default_request_options
17
+
18
+ module ClassMethods
19
+ def default_request_options;end
20
+
21
+ def retrieve_default_request_options(options)
22
+ RequestOptions.new(default_request_options, options).compiled
23
+ end
24
+
25
+ # Make a simple request and get a response back. Will build the
26
+ # response into this object
27
+ #
28
+ # For options see Typhoeus::Request
29
+ # args can be read like this: *paths, options = {}
30
+ def request(uri, *args)
31
+ options = args.extract_options!
32
+ build_request(uri_join(uri, *args), retrieve_default_request_options(options)).run
33
+ end
34
+
35
+ # Make a request, but use the default request_uri for this object
36
+ def from_base(*args)
37
+ request request_uri, *args
38
+ end
39
+
40
+ # Resource endpoint for this API
41
+ #
42
+ # base_url/organizations/1
43
+ def find(id, params = {})
44
+ from_base id, params
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end