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,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