acfs 0.50.0 → 1.0.0.dev.1.b297

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