acfs 1.0.0.dev.1.b305 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +5 -13
  2. data/CHANGELOG.md +64 -0
  3. data/README.md +2 -2
  4. data/acfs.gemspec +4 -4
  5. data/lib/acfs.rb +7 -5
  6. data/lib/acfs/adapter/base.rb +0 -2
  7. data/lib/acfs/adapter/typhoeus.rb +17 -13
  8. data/lib/acfs/collections/paginatable.rb +10 -10
  9. data/lib/acfs/configuration.rb +4 -5
  10. data/lib/acfs/errors.rb +10 -9
  11. data/lib/acfs/global.rb +16 -7
  12. data/lib/acfs/location.rb +11 -11
  13. data/lib/acfs/middleware/base.rb +1 -2
  14. data/lib/acfs/middleware/json.rb +27 -0
  15. data/lib/acfs/middleware/logger.rb +0 -2
  16. data/lib/acfs/middleware/msgpack.rb +30 -0
  17. data/lib/acfs/middleware/print.rb +0 -2
  18. data/lib/acfs/middleware/serializer.rb +39 -0
  19. data/lib/acfs/operation.rb +5 -5
  20. data/lib/acfs/request.rb +4 -4
  21. data/lib/acfs/request/callbacks.rb +2 -3
  22. data/lib/acfs/resource.rb +2 -2
  23. data/lib/acfs/resource/attributes.rb +10 -37
  24. data/lib/acfs/resource/attributes/base.rb +10 -19
  25. data/lib/acfs/resource/attributes/boolean.rb +10 -8
  26. data/lib/acfs/resource/attributes/date_time.rb +6 -9
  27. data/lib/acfs/resource/attributes/dict.rb +37 -0
  28. data/lib/acfs/resource/attributes/float.rb +11 -5
  29. data/lib/acfs/resource/attributes/integer.rb +7 -5
  30. data/lib/acfs/resource/attributes/list.rb +13 -6
  31. data/lib/acfs/resource/attributes/string.rb +3 -5
  32. data/lib/acfs/resource/attributes/uuid.rb +8 -17
  33. data/lib/acfs/resource/loadable.rb +0 -1
  34. data/lib/acfs/resource/locatable.rb +0 -4
  35. data/lib/acfs/resource/operational.rb +0 -2
  36. data/lib/acfs/resource/persistence.rb +17 -17
  37. data/lib/acfs/resource/query_methods.rb +3 -5
  38. data/lib/acfs/resource/service.rb +0 -2
  39. data/lib/acfs/resource/validation.rb +0 -2
  40. data/lib/acfs/response.rb +1 -2
  41. data/lib/acfs/response/formats.rb +1 -2
  42. data/lib/acfs/response/status.rb +3 -5
  43. data/lib/acfs/runner.rb +21 -11
  44. data/lib/acfs/service.rb +4 -6
  45. data/lib/acfs/service/middleware.rb +20 -30
  46. data/lib/acfs/service/middleware/stack.rb +63 -0
  47. data/lib/acfs/singleton_resource.rb +0 -2
  48. data/lib/acfs/stub.rb +30 -21
  49. data/lib/acfs/util.rb +1 -1
  50. data/lib/acfs/version.rb +4 -2
  51. data/spec/acfs/adapter/typhoeus_spec.rb +12 -4
  52. data/spec/acfs/collection_spec.rb +45 -33
  53. data/spec/acfs/configuration_spec.rb +9 -1
  54. data/spec/acfs/global_spec.rb +21 -3
  55. data/spec/acfs/middleware/json_spec.rb +63 -0
  56. data/spec/acfs/middleware/msgpack_spec.rb +60 -0
  57. data/spec/acfs/operation_spec.rb +10 -0
  58. data/spec/acfs/request/callbacks_spec.rb +8 -8
  59. data/spec/acfs/request_spec.rb +5 -5
  60. data/spec/acfs/resource/attributes/boolean_spec.rb +40 -9
  61. data/spec/acfs/resource/attributes/date_time_spec.rb +29 -35
  62. data/spec/acfs/resource/attributes/dict_spec.rb +75 -0
  63. data/spec/acfs/resource/attributes/float_spec.rb +48 -9
  64. data/spec/acfs/resource/attributes/integer_spec.rb +34 -0
  65. data/spec/acfs/resource/attributes/list_spec.rb +43 -19
  66. data/spec/acfs/resource/attributes/uuid_spec.rb +31 -54
  67. data/spec/acfs/resource/attributes_spec.rb +17 -31
  68. data/spec/acfs/resource/locatable_spec.rb +2 -2
  69. data/spec/acfs/resource/persistance_spec.rb +65 -34
  70. data/spec/acfs/resource/query_methods_spec.rb +87 -87
  71. data/spec/acfs/resource/validation_spec.rb +4 -5
  72. data/spec/acfs/response/formats_spec.rb +1 -1
  73. data/spec/acfs/response/status_spec.rb +1 -1
  74. data/spec/acfs/runner_spec.rb +28 -3
  75. data/spec/acfs/service/middleware_spec.rb +4 -20
  76. data/spec/acfs/service_spec.rb +3 -5
  77. data/spec/acfs/singleton_resource_spec.rb +1 -2
  78. data/spec/acfs/stub_spec.rb +137 -22
  79. data/spec/acfs_spec.rb +22 -19
  80. data/spec/spec_helper.rb +2 -1
  81. data/spec/support/service.rb +10 -6
  82. data/spec/support/shared/find_callbacks.rb +7 -7
  83. metadata +37 -30
  84. data/lib/acfs/middleware/json_decoder.rb +0 -16
  85. data/lib/acfs/middleware/json_encoder.rb +0 -20
  86. data/lib/acfs/middleware/msgpack_decoder.rb +0 -26
  87. data/lib/acfs/middleware/msgpack_encoder.rb +0 -19
  88. data/spec/acfs/middleware/json_decoder_spec.rb +0 -45
  89. data/spec/acfs/middleware/msgpack_decoder_spec.rb +0 -36
@@ -1,6 +1,5 @@
1
1
  module Acfs
2
2
  module Middleware
3
-
4
3
  # A base middleware that does not modify request or response.
5
4
  # Can be used as super class for custom middleware implementations.
6
5
  #
@@ -13,7 +12,7 @@ module Acfs
13
12
  end
14
13
 
15
14
  def call(request)
16
- request.on_complete { |res, nxt| response(res, nxt) } if respond_to? :response
15
+ request.on_complete {|res, nxt| response(res, nxt) } if respond_to? :response
17
16
  app.call(request)
18
17
  end
19
18
  end
@@ -0,0 +1,27 @@
1
+ require 'multi_json'
2
+
3
+ module Acfs
4
+ module Middleware
5
+ # A middleware to encore request data using JSON.
6
+ #
7
+ class JSON < Serializer
8
+ def mime
9
+ ::Mime::JSON
10
+ end
11
+
12
+ def encode(data)
13
+ ::MultiJson.dump data
14
+ end
15
+
16
+ def decode(body)
17
+ ::MultiJson.load body
18
+ end
19
+ end
20
+
21
+ # @deprecated
22
+ JsonDecoder = JSON
23
+
24
+ # @deprecated
25
+ JsonEncoder = JSON
26
+ end
27
+ end
@@ -2,11 +2,9 @@ require 'logger'
2
2
 
3
3
  module Acfs
4
4
  module Middleware
5
-
6
5
  # Log requests and responses.
7
6
  #
8
7
  class Logger < Base
9
-
10
8
  def initialize(app, options = {})
11
9
  super
12
10
  @logger = options[:logger] if options[:logger]
@@ -0,0 +1,30 @@
1
+ require 'msgpack'
2
+ require 'action_dispatch'
3
+
4
+ module Acfs
5
+ module Middleware
6
+ class MessagePack < Serializer
7
+ unless defined?(::Mime::MSGPACK)
8
+ ::Mime::Type.register 'application/x-msgpack', :msgpack
9
+ end
10
+
11
+ def mime
12
+ ::Mime::MSGPACK
13
+ end
14
+
15
+ def encode(data)
16
+ ::MessagePack.pack data
17
+ end
18
+
19
+ def decode(body)
20
+ ::MessagePack.unpack body
21
+ end
22
+ end
23
+
24
+ # @deprecated
25
+ MessagePackEncoder = MessagePack
26
+
27
+ # @deprecated
28
+ MessagePackDecoder = MessagePack
29
+ end
30
+ end
@@ -1,10 +1,8 @@
1
1
  module Acfs
2
2
  module Middleware
3
-
4
3
  # Print resquests and response on terminal
5
4
  #
6
5
  class Print < Base
7
-
8
6
  def call(req)
9
7
  puts '-' * 80
10
8
  puts req.inspect
@@ -0,0 +1,39 @@
1
+ module Acfs
2
+ module Middleware
3
+ # A base middleware that does not modify request or response.
4
+ # Can be used as super class for custom middleware implementations.
5
+ #
6
+ class Serializer < Base
7
+ def encode(_data)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def decode(_data)
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def mime
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def call(request)
20
+ unless request.headers['Content-Type']
21
+ request.body = encode request.data
22
+ request.headers['Content-Type'] = mime
23
+ end
24
+
25
+ accept = request.headers['Accept'].to_s.split(',')
26
+ accept << "#{mime};q=#{options.fetch(:q, 1)}"
27
+ request.headers['Accept'] = accept.join(',')
28
+
29
+ request.on_complete do |response, nxt|
30
+ response.data = decode response.body if mime == response.content_type
31
+
32
+ nxt.call response
33
+ end
34
+
35
+ app.call(request)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,5 +1,4 @@
1
1
  module Acfs
2
-
3
2
  # @api private
4
3
  #
5
4
  # Describes a CRUD operation. Handle request creation and response
@@ -51,15 +50,16 @@ module Acfs
51
50
  end
52
51
 
53
52
  def method
54
- { read: :get, list: :get, update: :put, create: :post, delete: :delete }[action]
53
+ {read: :get, list: :get, update: :put, create: :post, delete: :delete}[action]
55
54
  end
56
55
 
57
56
  def request
58
- request = ::Acfs::Request.new url, method: method, params: params, data: data
57
+ request = ::Acfs::Request.new url, method: method, params: params,
58
+ data: data, operation: self
59
59
  request.on_complete do |response|
60
60
  ::ActiveSupport::Notifications.instrument 'acfs.operation.complete',
61
- operation: self,
62
- response: response
61
+ operation: self,
62
+ response: response
63
63
 
64
64
  handle_failure response unless response.success?
65
65
  callback.call response.data, response
@@ -1,25 +1,25 @@
1
1
  require 'acfs/request/callbacks'
2
2
 
3
3
  module Acfs
4
-
5
4
  # Encapsulate all data required to make up a request to the
6
5
  # underlaying http library.
7
6
  #
8
7
  class Request
9
8
  attr_accessor :body, :format
10
- attr_reader :url, :headers, :params, :data, :method
9
+ attr_reader :url, :headers, :params, :data, :method, :operation
11
10
 
12
11
  include Request::Callbacks
13
12
 
14
13
  def initialize(url, options = {}, &block)
15
- @url = URI.parse(url.to_s).tap do |url|
14
+ @url = URI.parse(url.to_s).tap do |_url|
16
15
  @data = options.delete(:data) || nil
17
16
  @format = options.delete(:format) || :json
18
17
  @headers = options.delete(:headers) || {}
19
18
  @params = options.delete(:params) || {}
20
19
  @method = options.delete(:method) || :get
21
20
  end.to_s
22
- on_complete &block if block_given?
21
+ @operation = options.delete(:operation) || nil
22
+ on_complete(&block) if block_given?
23
23
  end
24
24
 
25
25
  def data?
@@ -1,6 +1,5 @@
1
1
  module Acfs
2
2
  class Request
3
-
4
3
  # Module containing callback handling for Requests.
5
4
  # Current the only callback type is `on_complete`:
6
5
  #
@@ -8,7 +7,6 @@ module Acfs
8
7
  # request.on_complete { |response| ... }
9
8
  #
10
9
  module Callbacks
11
-
12
10
  # Add a new `on_complete` callback for this request.
13
11
  #
14
12
  # @example Set on_complete.
@@ -43,8 +41,9 @@ module Acfs
43
41
  end
44
42
 
45
43
  private
44
+
46
45
  def call_callback(res, index)
47
- callbacks[index].call res, proc { |res| call_callback res, index + 1 } if index < callbacks.size
46
+ callbacks[index].call res, proc {|bres| call_callback bres, index + 1 } if index < callbacks.size
48
47
  end
49
48
  end
50
49
  end
@@ -17,8 +17,8 @@ class Acfs::Resource
17
17
  if ActiveModel::VERSION::MAJOR >= 4
18
18
  include ActiveModel::Model
19
19
  else
20
- extend ActiveModel::Naming
21
- extend ActiveModel::Translation
20
+ extend ActiveModel::Naming
21
+ extend ActiveModel::Translation
22
22
  include ActiveModel::Conversion
23
23
  include ActiveModel::Validations
24
24
  end
@@ -178,7 +178,6 @@ class Acfs::Resource
178
178
 
179
179
  #
180
180
  module ClassMethods
181
-
182
181
  ATTR_CLASS_BASE = '::Acfs::Resource::Attributes'.freeze
183
182
 
184
183
  #
@@ -223,56 +222,30 @@ class Acfs::Resource
223
222
  # Attributes with default values.
224
223
  #
225
224
  def attributes
226
- Hash.new.tap do |attrs|
227
- defined_attributes.each do |key, attr|
228
- attrs[key] = attr.default_value
229
- end
225
+ defined_attributes.each_with_object({}) do |(key, attr), hash|
226
+ hash[key] = attr.default_value
230
227
  end
231
228
  end
232
229
 
233
230
  def defined_attributes
234
- @attributes ||= begin
235
- attributes = {}
236
- if superclass.respond_to?(:defined_attributes)
237
- attributes.merge superclass.defined_attributes
238
- end
239
-
240
- attributes
231
+ if superclass.respond_to?(:defined_attributes)
232
+ superclass.defined_attributes.merge(local_attributes)
233
+ else
234
+ local_attributes
241
235
  end
242
236
  end
243
237
 
244
- # @api public
245
- #
246
- # Return hash of attributes and there types.
247
- #
248
- # @example
249
- # class User < Acfs::Resource
250
- # attribute :name, :string
251
- # attribute :age, :integer, default: 25
252
- # end
253
- # User.attributes # => {"name": Acfs::Model::Attributes::String,
254
- # # "age": Acfs::Model::Attributes::Integer}
255
- #
256
- # @return [Hash{Symbol => Class}] Attributes and their types.
257
- #
258
- def attribute_types
259
- @attribute_types ||= begin
260
- attribute_types = {}
261
- if superclass.respond_to?(:attribute_types)
262
- attribute_types.merge superclass.attribute_types
263
- end
238
+ private
264
239
 
265
- attribute_types
266
- end
240
+ def local_attributes
241
+ @local_attributes ||= {}
267
242
  end
268
243
 
269
- private
270
-
271
244
  def define_attribute(name, type, opts = {})
272
245
  name = name.to_s
273
246
  attribute = type.new opts
274
247
 
275
- defined_attributes[name] = attribute
248
+ local_attributes[name] = attribute
276
249
  define_attribute_method name
277
250
 
278
251
  send :define_method, name do
@@ -1,36 +1,27 @@
1
1
  module Acfs::Resource::Attributes
2
-
3
2
  #
4
3
  class Base
5
- attr_reader :options
6
-
7
- def initialize(opts = {})
8
- @options = opts
9
- @options.reverse_merge! allow_nil: true
10
- end
4
+ attr_reader :default
11
5
 
12
- def nil_allowed?
13
- options[:allow_nil]
6
+ def initialize(default: nil)
7
+ @default = default
14
8
  end
15
9
 
16
- def blank_allowed?
17
- options[:allow_blank]
10
+ def cast(value)
11
+ cast_value(value) unless value.nil?
18
12
  end
19
13
 
20
14
  def default_value
21
- if options[:default].is_a? Proc
22
- options[:default]
15
+ if default.respond_to? :call
16
+ default
23
17
  else
24
- cast options[:default]
18
+ cast default
25
19
  end
26
20
  end
27
21
 
28
- def cast(obj)
29
- return nil if obj.nil? && nil_allowed? || (obj == '' && blank_allowed?)
30
- cast_type obj
31
- end
22
+ private
32
23
 
33
- def cast_type(obj)
24
+ def cast_value(_value)
34
25
  raise NotImplementedError
35
26
  end
36
27
  end
@@ -1,5 +1,4 @@
1
1
  module Acfs::Resource::Attributes
2
-
3
2
  # @api public
4
3
  #
5
4
  # Boolean attribute type. Use it in your model as an attribute type:
@@ -15,21 +14,24 @@ module Acfs::Resource::Attributes
15
14
  # true, on, yes
16
15
  #
17
16
  class Boolean < Base
18
-
19
- TRUE_VALUES = %w(true on yes 1)
17
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF', 'no', 'NO'].to_set
20
18
 
21
19
  # @api public
22
20
  #
23
21
  # Cast given object to boolean.
24
22
  #
25
- # @param [Object] obj Object to cast.
23
+ # @param [Object] value Object to cast.
26
24
  # @return [TrueClass, FalseClass] Casted boolean.
27
25
  #
28
- def cast_type(obj)
29
- return true if obj.is_a? TrueClass
30
- return false if obj.is_a? FalseClass
26
+ def cast_value(value)
27
+ return true if value == true
28
+ return false if value == false
31
29
 
32
- TRUE_VALUES.include? obj.to_s
30
+ if value.blank?
31
+ nil
32
+ else
33
+ !FALSE_VALUES.include?(value)
34
+ end
33
35
  end
34
36
  end
35
37
  end
@@ -1,5 +1,4 @@
1
1
  module Acfs::Resource::Attributes
2
-
3
2
  # @api public
4
3
  #
5
4
  # DateTime attribute type. Use it in your model as
@@ -16,18 +15,16 @@ module Acfs::Resource::Attributes
16
15
  #
17
16
  # Cast given object to DateTime.
18
17
  #
19
- # @param [Object] obj Object to cast.
18
+ # @param [Object] value Object to cast.
20
19
  # @return [DateTime] Casted object as DateTime.
21
20
  #
22
- def cast_type(obj)
23
- if nil_allowed? && obj.blank?
21
+ def cast_value(value)
22
+ if value.blank?
24
23
  nil
25
- elsif obj.is_a? ::DateTime
26
- obj
27
- elsif obj.is_a?(Time) || obj.is_a?(Date)
28
- ::DateTime.iso8601 obj.iso8601
24
+ elsif value.acts_like?(:time) || value.acts_like?(:date)
25
+ value.to_datetime
29
26
  else
30
- ::DateTime.iso8601 obj
27
+ ::DateTime.iso8601 value
31
28
  end
32
29
  end
33
30
  end
@@ -0,0 +1,37 @@
1
+ module Acfs::Resource::Attributes
2
+ # @api public
3
+ #
4
+ # Dict attribute type. Use it in your model as an attribute type:
5
+ #
6
+ # @example
7
+ # class User
8
+ # include Acfs::Model
9
+ # attribute :opts, :dict
10
+ # end
11
+ #
12
+ class Dict < Base
13
+ # @api public
14
+ #
15
+ # Cast given object to a dict/hash.
16
+ #
17
+ # @param [Object] value Object to cast.
18
+ # @return [Hash] Casted object as hash.
19
+ # @raise [TypeError] If object cannot be casted to a hash.
20
+ #
21
+ def cast_value(value)
22
+ return {} if value.blank?
23
+
24
+ if value.is_a?(Hash)
25
+ value
26
+ elsif value.respond_to?(:serializable_hash)
27
+ value.serializable_hash
28
+ elsif value.respond_to?(:to_hash)
29
+ value.to_hash
30
+ elsif value.respond_to?(:to_h)
31
+ value.to_h
32
+ else
33
+ Hash(value)
34
+ end
35
+ end
36
+ end
37
+ end