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,64 @@
1
+ module Axel
2
+ module Associations
3
+ class Base
4
+ attr_accessor :model
5
+ attr_accessor :relation_name
6
+ attr_accessor :options
7
+
8
+ def initialize(model, relation_name, options)
9
+ self.model = model
10
+ self.relation_name = relation_name.to_s
11
+ self.options = options || {}
12
+ end
13
+
14
+ def handles_method?(method_name)
15
+ matchers.any? { |matcher| method_name.to_s.match(matcher) }
16
+ end
17
+
18
+ def run_method(instance, method_name, *args, &block)
19
+ if included?
20
+ included_getter(instance, *args, &block)
21
+ else
22
+ method_chooser(method_name)[instance, *args, &block]
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def included?
29
+ !!options[:included]
30
+ end
31
+
32
+ def build_klass
33
+ options[:class] || relation_name_klass
34
+ end
35
+
36
+ def premodule
37
+ model.name.split("::")[0..-2].join("::")
38
+ end
39
+
40
+ def relation_name_klass
41
+ "#{premodule}::#{relation_name.to_s.singularize.classify}".safe_constantize
42
+ end
43
+
44
+ def matcher_map
45
+ {
46
+ /\A#{relation_name}\z/ => :getter
47
+ }
48
+ end
49
+
50
+ def matchers
51
+ matcher_map.keys
52
+ end
53
+
54
+ def method_chooser(method_name)
55
+ name = matcher_map[matchers.find { |matcher| method_name.to_s.match matcher }]
56
+ if name
57
+ method(name)
58
+ else
59
+ raise NoMethodError, "Could not find an association method for `#{method_name}'"
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,43 @@
1
+ module Axel
2
+ module Associations
3
+ class BelongsTo < Base
4
+
5
+ private
6
+
7
+ def included_getter(instance, *args, &block)
8
+ relation_attributes = (instance.try(:attributes) || {})[relation_name]
9
+ build_klass.new(relation_attributes) if relation_attributes
10
+ end
11
+
12
+ def find_nested?
13
+ !!options[:find_nested]
14
+ end
15
+
16
+ def getter(instance, *args, &block)
17
+ request_options = args.extract_options!
18
+ if find_nested?
19
+ build_klass
20
+ .querier
21
+ .without_default_path
22
+ .at_path(route_path(instance))
23
+ .request_options(request_options)
24
+ .first
25
+ else
26
+ build_klass.find(instance.public_send(association_id_method), request_options)
27
+ end
28
+ end
29
+
30
+ def route_path(instance)
31
+ [URI(instance.request_uri).path, association_path(instance)].join("/")
32
+ end
33
+
34
+ def association_path(instance) # needs to be belongs_to, some looks has_many
35
+ "#{relation_name.to_s.pluralize}/#{instance.public_send(association_id_method)}"
36
+ end
37
+
38
+ def association_id_method
39
+ options[:id_attribute] || "#{relation_name.to_s.singularize.underscore}_id"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,29 @@
1
+ module Axel
2
+ module Associations
3
+ class HasMany < Base
4
+
5
+ private
6
+
7
+ def included_getter(instance, *args, &block)
8
+ Array(instance.attributes[relation_name]).map { |attributes| build_klass.new(attributes) }
9
+ end
10
+
11
+ def getter(instance, *args, &block)
12
+ request_options = args.extract_options!
13
+ build_klass
14
+ .querier
15
+ .without_default_path
16
+ .at_path(route_path(instance))
17
+ .request_options request_options
18
+ end
19
+
20
+ def route_path(instance)
21
+ [URI(instance.request_uri).path, association_path].join("/")
22
+ end
23
+
24
+ def association_path # needs to be belongs_to, some looks has_many
25
+ options[:suffix_path].present? && options[:suffix_path] || relation_name.to_s
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ module Axel
2
+ module Associations
3
+ class HasOne < Base
4
+
5
+ private
6
+
7
+ def included_getter(instance, *args, &block)
8
+ Array(instance.attributes[relation_name]).map { |attributes| build_klass.new(attributes) }.first
9
+ end
10
+
11
+ def getter(instance, *args, &block)
12
+ request_options = args.extract_options!
13
+ build_klass
14
+ .querier
15
+ .without_default_path
16
+ .at_path(route_path(instance))
17
+ .request_options(request_options)
18
+ .first
19
+ end
20
+
21
+ def route_path(instance)
22
+ [URI(instance.request_uri).path, association_path].join("/")
23
+ end
24
+
25
+ def association_path # needs to be belongs_to, some looks has_many
26
+ options[:suffix_path].present? && options[:suffix_path] || relation_name.to_s
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ module Axel
2
+ module Payload
3
+ end
4
+ end
@@ -0,0 +1,107 @@
1
+ module Axel
2
+ module Payload
3
+ class Base
4
+ attr_reader :attributes
5
+
6
+ delegate :fetch, to: :attributes
7
+
8
+ def self.root_node(name = nil)
9
+ @root_node = name if name
10
+ @root_node
11
+ end
12
+
13
+ def self.attribute(name, options = {})
14
+ attribute_defaults[name.to_s.to_sym] = options[:default]
15
+ visibility_read = options.has_key?(:read) ? options[:read] : true
16
+ visibility_write = options.has_key?(:write) ? options[:write] : true
17
+ if visibility_read
18
+ define_method name do
19
+ @attributes[name]
20
+ end
21
+ if %w[public private protected].include? visibility_read.to_s
22
+ send visibility_read, name
23
+ end
24
+ end
25
+
26
+ if visibility_write
27
+ define_method "#{name}=" do |new_value|
28
+ @attributes[name] = new_value
29
+ end
30
+ if %w[public private protected].include? visibility_write.to_s
31
+ send visibility_write, name
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.attribute_defaults
37
+ @attribute_defaults ||= {}
38
+ end
39
+
40
+ def reset!
41
+ @attributes = {}
42
+ apply_defaults
43
+ self
44
+ end
45
+
46
+ def initialize(params = {})
47
+ apply_defaults
48
+ params = params.is_a?(Hash) ? params : {}
49
+ @attributes.merge! params
50
+ end
51
+
52
+ def []=(key, value)
53
+ @attributes[key] = decode(value)
54
+ end
55
+
56
+ def [](key)
57
+ @attributes[key]
58
+ end
59
+
60
+ def display
61
+ drop? ? {} : @attributes
62
+ end
63
+
64
+ def display?
65
+ true
66
+ end
67
+
68
+ def drop?
69
+ !!@drop
70
+ end
71
+
72
+ def drop!
73
+ @drop = true
74
+ end
75
+
76
+ def to_json
77
+ display? ? display.to_json : ""
78
+ end
79
+
80
+ def to_xml
81
+ display? ? display.to_xml(dasherize: false, skip_instruct: true, root: self.class.root_node) : ""
82
+ end
83
+
84
+ def apply_defaults
85
+ @attributes = self.class.attribute_defaults.with_indifferent_access
86
+ end
87
+ private :apply_defaults
88
+
89
+ def decode(input)
90
+ if input.is_a? String
91
+ begin
92
+ ActiveSupport::JSON.decode input
93
+ rescue MultiJson::DecodeError, JSON::ParserError
94
+ begin
95
+ Hash.from_xml input
96
+ rescue REXML::ParseException
97
+ input
98
+ end
99
+ end
100
+ else
101
+ input
102
+ end
103
+ end
104
+ private :decode
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,62 @@
1
+ module Axel
2
+ module Payload
3
+ class Errors < Base
4
+ root_node :error
5
+ attribute :messages, default: []
6
+ attribute :status, default: 200, read: :private
7
+ SUCCESS_CODES = [200, 201, 202, 204]
8
+
9
+ def initialize(params = {})
10
+ super
11
+ @suppress_response_codes = !!@attributes[:suppress_response_codes]
12
+ end
13
+
14
+ def exception
15
+ RemoteError.new(self) unless success?
16
+ end
17
+
18
+ def success?
19
+ SUCCESS_CODES.include?(status_code.to_i) && messages.empty?
20
+ end
21
+
22
+ def messages=(messages)
23
+ @attributes[:messages] = [messages].flatten.compact
24
+ end
25
+
26
+ def status=(status)
27
+ @attributes[:status] = status
28
+ end
29
+
30
+ def header_status
31
+ suppress_response_codes? ? 200 : status_code
32
+ end
33
+
34
+ def <<(message)
35
+ @attributes[:messages] << message
36
+ end
37
+
38
+ def display
39
+ drop? ? {} : { status: status_code, messages: messages }
40
+ end
41
+
42
+ def display?
43
+ !SUCCESS_CODES.include?(status_code.to_i) || messages.present?
44
+ end
45
+
46
+ def new_error(status, *messages)
47
+ self.status = status if status
48
+ self.messages = self.messages + messages
49
+ end
50
+
51
+ def status_code
52
+ status ? Rack::Utils.status_code(status) : 200
53
+ end
54
+
55
+ private
56
+
57
+ def suppress_response_codes?
58
+ @suppress_response_codes
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,57 @@
1
+ module Axel
2
+ module Payload
3
+ class Metadata < Base
4
+ root_node :metadata
5
+
6
+ def paged?
7
+ total_pages > 1
8
+ end
9
+
10
+ def total_pages
11
+ raw = pagination_settings["total_pages"].to_i
12
+ raw == 0 ? 1 : raw
13
+ end
14
+
15
+ def merge!(other_object)
16
+ tap do
17
+ @attributes.merge! mergeable_from(other_object)
18
+ end
19
+ end
20
+
21
+ def merge(other_object)
22
+ #self.class.new(attributes).tap do |c|
23
+ dup.tap do |c|
24
+ c.merge!(mergeable_from(other_object))
25
+ end
26
+ end
27
+
28
+ def dup(*args)
29
+ super.tap do |d|
30
+ d.instance_variable_set "@attributes", @attributes.clone
31
+ end
32
+ end
33
+
34
+ def clone(*args)
35
+ super.tap do |c|
36
+ c.instance_variable_set "@attributes", @attributes.clone
37
+ end
38
+ end
39
+
40
+ def pagination_settings
41
+ fetch("pagination", {})
42
+ end
43
+ private :pagination_settings
44
+
45
+ def mergeable_from(object)
46
+ if object.respond_to?(:attributes) && object.attributes.is_a?(Hash)
47
+ object.attributes
48
+ elsif object.is_a? Hash
49
+ object
50
+ else
51
+ {} # unmergeable, should think about erroring
52
+ end
53
+ end
54
+ private :mergeable_from
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,166 @@
1
+ module Axel
2
+ class Querier
3
+ private
4
+ attr_writer :klass
5
+ attr_writer :loaded
6
+ attr_reader :records
7
+
8
+ protected
9
+ attr_accessor :where_values
10
+ attr_accessor :manual_uri
11
+ attr_accessor :extra_paths
12
+ attr_accessor :request_option_values
13
+ attr_accessor :paged
14
+ attr_writer :records
15
+
16
+ public
17
+ attr_reader :klass
18
+ attr_reader :loaded
19
+
20
+ alias loaded? loaded
21
+ alias paged? paged
22
+
23
+ def self.query_methods
24
+ [
25
+ :all,
26
+ :none,
27
+ :where,
28
+ :uri,
29
+ :to_a,
30
+ :at_path,
31
+ :request_options,
32
+ :without_default_path,
33
+ :paged
34
+ ]
35
+ end
36
+
37
+ def initialize(klass)
38
+ self.klass = klass
39
+ self.records = []
40
+ self.extra_paths = []
41
+ self.loaded = false
42
+ self.where_values = {}.with_indifferent_access
43
+ self.request_option_values = {}.with_indifferent_access
44
+ end
45
+
46
+ def paged
47
+ query = clone
48
+ query.paged = true
49
+ query
50
+ end
51
+
52
+ def at_path(*paths)
53
+ return self if paths.blank?
54
+ query = clone
55
+ query.extra_paths += paths
56
+ query
57
+ end
58
+
59
+ # "http://user-service.dev"
60
+ def uri(base_url)
61
+ return self if base_url.blank?
62
+ query = clone
63
+ query.manual_uri = base_url.to_s
64
+ query
65
+ end
66
+
67
+ def where(params = {})
68
+ return self if params.blank? || !params.is_a?(Hash)
69
+ query = clone
70
+ query.request_option_values.deep_merge! params: params
71
+ query
72
+ end
73
+
74
+ def request_options(params = {})
75
+ return self if params.blank? || !params.is_a?(Hash)
76
+ query = clone
77
+ query.request_option_values.deep_merge! params
78
+ query
79
+ end
80
+ alias_method :all, :request_options
81
+
82
+ def none
83
+ query = clone
84
+ query.loaded!
85
+ query.records = []
86
+ query
87
+ end
88
+
89
+ def without_default_path
90
+ query = clone
91
+ query.manual_uri = klass.site
92
+ query
93
+ end
94
+
95
+ def reload
96
+ reset
97
+ self
98
+ end
99
+
100
+ def to_a
101
+ Array(loaded? ? records : self.records = run_requests)
102
+ end
103
+
104
+ def inspect
105
+ to_a.inspect
106
+ end
107
+
108
+ def loaded!
109
+ self.loaded = true
110
+ end
111
+ protected :loaded!
112
+
113
+ def reset
114
+ self.loaded = false
115
+ self.records = []
116
+ self
117
+ end
118
+ private :reset
119
+
120
+ def request_uri
121
+ Uri.new((manual_uri || klass.request_uri), *extra_paths).to_s
122
+ end
123
+ private :request_uri
124
+
125
+ def retrieve_request_options
126
+ klass.retrieve_default_request_options request_option_values
127
+ end
128
+ private :retrieve_request_options
129
+
130
+ def run_requests
131
+ loaded!
132
+ execute_request_on_klass
133
+ end
134
+ private :run_requests
135
+
136
+ def execute_request_on_klass
137
+ initial = klass.request(request_uri, retrieve_request_options)
138
+ return initial unless paged?
139
+
140
+ single = initial.first
141
+ return initial if single.blank? || !single.paged?
142
+
143
+ (2..single.total_pages).reduce(initial) do |memo, page|
144
+ paged_options = retrieve_request_options
145
+ .stringify_keys
146
+ .deep_merge "params" => { "page" => page }
147
+ memo + klass.request(request_uri, paged_options)
148
+ end
149
+ end
150
+ private :execute_request_on_klass
151
+
152
+ def respond_to?(method, include_private = false)
153
+ super || Array.public_method_defined?(method) || klass.routes.has_key?(method)
154
+ end
155
+
156
+ def method_missing(method, *args, &block)
157
+ if Array.method_defined?(method)
158
+ to_a.public_send(method, *args, &block)
159
+ elsif klass.routes.has_key? method
160
+ klass.public_send method, *args
161
+ else
162
+ super
163
+ end
164
+ end
165
+ end
166
+ end