acfs 0.50.0 → 1.0.0.dev.1.b297

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