axel 0.0.1

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