acfs 1.3.3 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +372 -0
  3. data/LICENSE +22 -0
  4. data/README.md +321 -0
  5. data/acfs.gemspec +38 -0
  6. data/lib/acfs.rb +51 -0
  7. data/lib/acfs/adapter/base.rb +26 -0
  8. data/lib/acfs/adapter/typhoeus.rb +82 -0
  9. data/lib/acfs/collection.rb +28 -0
  10. data/lib/acfs/collections/paginatable.rb +76 -0
  11. data/lib/acfs/configuration.rb +120 -0
  12. data/lib/acfs/errors.rb +147 -0
  13. data/lib/acfs/global.rb +101 -0
  14. data/lib/acfs/location.rb +76 -0
  15. data/lib/acfs/middleware/base.rb +24 -0
  16. data/lib/acfs/middleware/json.rb +31 -0
  17. data/lib/acfs/middleware/logger.rb +23 -0
  18. data/lib/acfs/middleware/msgpack.rb +32 -0
  19. data/lib/acfs/middleware/print.rb +23 -0
  20. data/lib/acfs/middleware/serializer.rb +41 -0
  21. data/lib/acfs/operation.rb +96 -0
  22. data/lib/acfs/request.rb +32 -0
  23. data/lib/acfs/request/callbacks.rb +54 -0
  24. data/lib/acfs/resource.rb +39 -0
  25. data/lib/acfs/resource/attributes.rb +270 -0
  26. data/lib/acfs/resource/attributes/base.rb +29 -0
  27. data/lib/acfs/resource/attributes/boolean.rb +39 -0
  28. data/lib/acfs/resource/attributes/date_time.rb +32 -0
  29. data/lib/acfs/resource/attributes/dict.rb +39 -0
  30. data/lib/acfs/resource/attributes/float.rb +33 -0
  31. data/lib/acfs/resource/attributes/integer.rb +29 -0
  32. data/lib/acfs/resource/attributes/list.rb +36 -0
  33. data/lib/acfs/resource/attributes/string.rb +26 -0
  34. data/lib/acfs/resource/attributes/uuid.rb +48 -0
  35. data/lib/acfs/resource/dirty.rb +37 -0
  36. data/lib/acfs/resource/initialization.rb +31 -0
  37. data/lib/acfs/resource/loadable.rb +35 -0
  38. data/lib/acfs/resource/locatable.rb +135 -0
  39. data/lib/acfs/resource/operational.rb +26 -0
  40. data/lib/acfs/resource/persistence.rb +258 -0
  41. data/lib/acfs/resource/query_methods.rb +266 -0
  42. data/lib/acfs/resource/service.rb +44 -0
  43. data/lib/acfs/resource/validation.rb +49 -0
  44. data/lib/acfs/response.rb +30 -0
  45. data/lib/acfs/response/formats.rb +27 -0
  46. data/lib/acfs/response/status.rb +33 -0
  47. data/lib/acfs/rspec.rb +13 -0
  48. data/lib/acfs/runner.rb +102 -0
  49. data/lib/acfs/service.rb +94 -0
  50. data/lib/acfs/service/middleware.rb +58 -0
  51. data/lib/acfs/service/middleware/stack.rb +65 -0
  52. data/lib/acfs/singleton_resource.rb +85 -0
  53. data/lib/acfs/stub.rb +199 -0
  54. data/lib/acfs/util.rb +22 -0
  55. data/lib/acfs/version.rb +16 -0
  56. data/lib/acfs/yard.rb +6 -0
  57. data/spec/acfs/adapter/typhoeus_spec.rb +55 -0
  58. data/spec/acfs/collection_spec.rb +157 -0
  59. data/spec/acfs/configuration_spec.rb +53 -0
  60. data/spec/acfs/global_spec.rb +140 -0
  61. data/spec/acfs/location_spec.rb +25 -0
  62. data/spec/acfs/middleware/json_spec.rb +79 -0
  63. data/spec/acfs/middleware/msgpack_spec.rb +62 -0
  64. data/spec/acfs/operation_spec.rb +12 -0
  65. data/spec/acfs/request/callbacks_spec.rb +48 -0
  66. data/spec/acfs/request_spec.rb +79 -0
  67. data/spec/acfs/resource/attributes/boolean_spec.rb +58 -0
  68. data/spec/acfs/resource/attributes/date_time_spec.rb +51 -0
  69. data/spec/acfs/resource/attributes/dict_spec.rb +77 -0
  70. data/spec/acfs/resource/attributes/float_spec.rb +61 -0
  71. data/spec/acfs/resource/attributes/integer_spec.rb +36 -0
  72. data/spec/acfs/resource/attributes/list_spec.rb +60 -0
  73. data/spec/acfs/resource/attributes/uuid_spec.rb +42 -0
  74. data/spec/acfs/resource/attributes_spec.rb +179 -0
  75. data/spec/acfs/resource/dirty_spec.rb +49 -0
  76. data/spec/acfs/resource/initialization_spec.rb +36 -0
  77. data/spec/acfs/resource/loadable_spec.rb +22 -0
  78. data/spec/acfs/resource/locatable_spec.rb +118 -0
  79. data/spec/acfs/resource/persistance_spec.rb +322 -0
  80. data/spec/acfs/resource/query_methods_spec.rb +548 -0
  81. data/spec/acfs/resource/validation_spec.rb +129 -0
  82. data/spec/acfs/response/formats_spec.rb +52 -0
  83. data/spec/acfs/response/status_spec.rb +71 -0
  84. data/spec/acfs/runner_spec.rb +95 -0
  85. data/spec/acfs/service/middleware_spec.rb +35 -0
  86. data/spec/acfs/service_spec.rb +48 -0
  87. data/spec/acfs/singleton_resource_spec.rb +17 -0
  88. data/spec/acfs/stub_spec.rb +345 -0
  89. data/spec/acfs_spec.rb +205 -0
  90. data/spec/fixtures/config.yml +14 -0
  91. data/spec/spec_helper.rb +42 -0
  92. data/spec/support/hash.rb +11 -0
  93. data/spec/support/response.rb +12 -0
  94. data/spec/support/service.rb +92 -0
  95. data/spec/support/shared/find_callbacks.rb +50 -0
  96. metadata +159 -26
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Acfs::Resource::Attributes
4
+ # @api public
5
+ #
6
+ # DateTime attribute type. Use it in your model as
7
+ # an attribute type:
8
+ #
9
+ # @example
10
+ # class User < Acfs::Resource
11
+ # attribute :name, :date_time
12
+ # end
13
+ #
14
+ class DateTime < Base
15
+ # @api public
16
+ #
17
+ # Cast given object to DateTime.
18
+ #
19
+ # @param [Object] value Object to cast.
20
+ # @return [DateTime] Casted object as DateTime.
21
+ #
22
+ def cast_value(value)
23
+ if value.blank?
24
+ nil
25
+ elsif !value.is_a?(::String) && value.respond_to?(:to_datetime)
26
+ value.to_datetime
27
+ else
28
+ ::DateTime.iso8601 value
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Acfs::Resource::Attributes
4
+ # @api public
5
+ #
6
+ # Dict attribute type. Use it in your model as an attribute type:
7
+ #
8
+ # @example
9
+ # class User
10
+ # include Acfs::Model
11
+ # attribute :opts, :dict
12
+ # end
13
+ #
14
+ class Dict < Base
15
+ # @api public
16
+ #
17
+ # Cast given object to a dict/hash.
18
+ #
19
+ # @param [Object] value Object to cast.
20
+ # @return [Hash] Casted object as hash.
21
+ # @raise [TypeError] If object cannot be casted to a hash.
22
+ #
23
+ def cast_value(value)
24
+ return {} if value.blank?
25
+
26
+ if value.is_a?(Hash)
27
+ value
28
+ elsif value.respond_to?(:serializable_hash)
29
+ value.serializable_hash
30
+ elsif value.respond_to?(:to_hash)
31
+ value.to_hash
32
+ elsif value.respond_to?(:to_h)
33
+ value.to_h
34
+ else
35
+ Hash(value)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Acfs::Resource::Attributes
4
+ # @api public
5
+ #
6
+ # Float attribute type. Use it in your model as an attribute type:
7
+ #
8
+ # @example
9
+ # class User < Acfs::Resource
10
+ # attribute :name, :float
11
+ # end
12
+ #
13
+ class Float < Base
14
+ # @api public
15
+ #
16
+ # Cast given object to float.
17
+ #
18
+ # @param [Object] value Object to cast.
19
+ # @return [Float] Casted object as float.
20
+ #
21
+ def cast_value(value)
22
+ return 0.0 if value.blank?
23
+
24
+ case value
25
+ when ::Float then value
26
+ when 'Infinity' then ::Float::INFINITY
27
+ when '-Infinity' then -::Float::INFINITY
28
+ when 'NaN' then ::Float::NAN
29
+ else Float(value)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Acfs::Resource::Attributes
4
+ # @api public
5
+ #
6
+ # Integer attribute type. Use it in your model as an attribute type:
7
+ #
8
+ # @example
9
+ # class User < Acfs::Resource
10
+ # attribute :name, :integer
11
+ # end
12
+ #
13
+ class Integer < Base
14
+ # @api public
15
+ #
16
+ # Cast given object to integer.
17
+ #
18
+ # @param [Object] value Object to cast.
19
+ # @return [Fixnum] Casted object as fixnum.
20
+ #
21
+ def cast_value(value)
22
+ if value.blank?
23
+ 0
24
+ else
25
+ Integer(value)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Acfs::Resource::Attributes
4
+ # @api public
5
+ #
6
+ # List attribute type. Use it in your model as an attribute type:
7
+ #
8
+ # @example
9
+ # class User < Acfs::Resource
10
+ # attribute :name, :list
11
+ # end
12
+ #
13
+ class List < Base
14
+ # @api public
15
+ #
16
+ # Cast given object to a list.
17
+ #
18
+ # @param [Object] value Object to cast.
19
+ # @return [Fixnum] Casted object as list.
20
+ # @raise [TypeError] If object cannot be casted to a list.
21
+ #
22
+ def cast_value(value)
23
+ return [] if value.blank?
24
+
25
+ if value.is_a?(::Array)
26
+ value
27
+ elsif value.respond_to?(:to_ary)
28
+ value.to_ary
29
+ elsif value.respond_to?(:to_a)
30
+ value.to_a
31
+ else
32
+ Array(value)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Acfs::Resource::Attributes
4
+ # @api public
5
+ #
6
+ # String attribute type. Use it in your model as
7
+ # an attribute type:
8
+ #
9
+ # @example
10
+ # class User < Acfs::Resource
11
+ # attribute :name, :string
12
+ # end
13
+ #
14
+ class String < Base
15
+ # @api public
16
+ #
17
+ # Cast given object to string.
18
+ #
19
+ # @param [Object] value Object to cast.
20
+ # @return [String] Casted string.
21
+ #
22
+ def cast_value(value)
23
+ value.to_s
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Acfs::Resource::Attributes
4
+ # @api public
5
+ #
6
+ # UUID attribute type. Use it in your model as an attribute type:
7
+ #
8
+ # @example
9
+ # class User < Acfs::Resource
10
+ # attribute :id, :uuid
11
+ # end
12
+ #
13
+ class UUID < Base
14
+ UUID_REGEXP = /[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}/i.freeze
15
+
16
+ # @api public
17
+ #
18
+ # Check if given object looks like a UUID, eg:
19
+ # `450b7a40-94ad-11e3-baa8-0800200c9a66`
20
+ # Valid UUIDs are 16 byte numbers represented as
21
+ # a hexadecimal string in five sub-groups seperated
22
+ # by a dash. Each group has to consist of a fixed
23
+ # number of hexadecimal digits:
24
+ # | Group | Digits |
25
+ # | -----:|:------ |
26
+ # | 1 | 8 |
27
+ # | 2 | 4 |
28
+ # | 3 | 4 |
29
+ # | 4 | 4 |
30
+ # | 5 | 12 |
31
+ #
32
+ # @param [Object] value Object to cast.
33
+ # @return [String] Casted object as UUID.
34
+ #
35
+ def cast_value(value)
36
+ if value.blank?
37
+ nil
38
+ elsif value.to_s =~ UUID_REGEXP
39
+ value
40
+ else
41
+ raise TypeError.new "Invalid UUID: `#{value}'"
42
+ end
43
+ end
44
+ end
45
+
46
+ # Lower-case alias for automatic type lookup
47
+ Uuid = UUID
48
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Acfs::Resource
4
+ #
5
+ # Thin wrapper around ActiveModel::Dirty
6
+ #
7
+ module Dirty
8
+ extend ActiveSupport::Concern
9
+ include ActiveModel::Dirty
10
+
11
+ # @api private
12
+ #
13
+ def reset_changes
14
+ clear_changes_information
15
+ end
16
+
17
+ # @api private
18
+ #
19
+ def save!(**kwargs)
20
+ super(**kwargs).tap {|_| changes_applied }
21
+ end
22
+
23
+ # @api private
24
+ #
25
+ def loaded!
26
+ reset_changes
27
+ super
28
+ end
29
+
30
+ # @api private
31
+ #
32
+ def write_raw_attribute(name, value, opts = {})
33
+ attribute_will_change!(name) if opts[:change].nil? || opts[:change]
34
+ super
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Acfs::Resource
4
+ #
5
+ # Initialization drop-in for pre-4.0 ActiveModel.
6
+ #
7
+ module Initialization
8
+ #
9
+ # @api public
10
+ #
11
+ # Initializes a new model with the given `params`.
12
+ #
13
+ # @example
14
+ # class User < Acfs::Resource
15
+ # attribute :name
16
+ # attribute :email, default: ->{ "#{name}@dom.tld" }
17
+ # attribute :age, :integer, default: 18
18
+ # end
19
+ #
20
+ # user = User.new({name: 'bob'})
21
+ # user.name # => "bob"
22
+ # user.email # => "bob@dom.tld"
23
+ # user.age # => 18
24
+ #
25
+ # @param attributes [Hash{Symbol => Object}] Attributes to set on resource.
26
+ #
27
+ def initialize(attributes = {})
28
+ write_attributes(attributes) if attributes
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Acfs::Resource
4
+ # Provides method to check for loading state of resources.
5
+ # A resource that is created but not yet fetched will be loaded
6
+ # after running {Acfs::Global#run Acfs.run}.
7
+ #
8
+ # @example
9
+ # user = User.find 5
10
+ # user.loaded? # => false
11
+ # Acfs.run
12
+ # user.loaded? # => true
13
+ #
14
+ module Loadable
15
+ extend ActiveSupport::Concern
16
+
17
+ # @api public
18
+ #
19
+ # Check if model is loaded or if request is still queued.
20
+ #
21
+ # @return [Boolean] True if resource is loaded, false otherwise.
22
+ #
23
+ def loaded?
24
+ @loaded.nil? ? false : @loaded
25
+ end
26
+
27
+ # @api private
28
+ #
29
+ # Mark model as loaded.
30
+ #
31
+ def loaded!
32
+ @loaded = true
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Acfs::Resource
4
+ # Provide methods for generation URLs for resources.
5
+ #
6
+ # @example
7
+ # class User
8
+ # service AccountService # With base URL `http://acc.svr`
9
+ # end
10
+ # User.url # => "http://acc.svr/users"
11
+ # User.url(5) # => "http://acc.svr/users/5"
12
+ #
13
+ module Locatable
14
+ extend ActiveSupport::Concern
15
+
16
+ module ClassMethods
17
+ # @overload url(suffix)
18
+ # @deprecated
19
+ # Return URL for this class of resource. Given suffix
20
+ # will be appended.
21
+ #
22
+ # @example
23
+ # User.url # => "http://users.srv.org/users"
24
+ # User.url(5) # => "http://users.srv.org/users/5"
25
+ #
26
+ # @param suffix [String] Suffix to append to URL.
27
+ # @return [String] Generated URL.
28
+ #
29
+ # @overload url(opts = {})
30
+ # Return URL for this class of resources. Given options
31
+ # will be used to replace URL path arguments and to
32
+ # determine the operation action.
33
+ #
34
+ # @example
35
+ # User.url(id: 5, action: :read) # => "http://users.srv.org/users/5"
36
+ # User.url(action: :list) # => "http://users.srv.org/users"
37
+ #
38
+ # @param opts [Hash] Options.
39
+ # @option opts [Symbol] :action Operation action,
40
+ # usually `:list`, `:create`, `:read`, `:update` or`:delete`.
41
+ # @return [String] Generated URL.
42
+ #
43
+ def url(suffix = nil, **opts)
44
+ if suffix.is_a? Hash
45
+ opts = suffix
46
+ suffix = nil
47
+ end
48
+
49
+ kwargs = {}
50
+ kwargs[:path] = opts[:path] if opts.key?(:path)
51
+ kwargs[:action] = opts.delete(:action) if opts.key?(:action)
52
+ kwargs[:action] = :list if suffix
53
+
54
+ url = location(**kwargs).build(opts.stringify_keys).str
55
+ url += "/#{suffix}" if suffix.to_s.present?
56
+ url
57
+ end
58
+
59
+ # Return a location object able to build the URL for this
60
+ # resource and given action.
61
+ #
62
+ # @example
63
+ # class Identity < ::Acfs::Resource
64
+ # service MyService, path: 'users/:user_id/identities'
65
+ # end
66
+ #
67
+ # location = Identity.location(action: :read)
68
+ # location.arguments
69
+ # => [:user_id, :id]
70
+ #
71
+ # location.raw_url
72
+ # => 'http://service/users/:user_id/identities/:id'
73
+ #
74
+ # location = Identity.location(action: :list)
75
+ # location.arguments
76
+ # => [:user_id]
77
+ #
78
+ # location.build(user_id: 42)
79
+ # => 'http://service/users/42/identities'
80
+ #
81
+ # @param opts [Hash] Options.
82
+ # @option opts [Symbol] :action Operation action,
83
+ # usually `:list`, `:create`, `:read`, `:update` or`:delete`.
84
+ #
85
+ # @return [Location] Location object.
86
+ #
87
+ def location(**opts)
88
+ service.location(self, **opts)
89
+ end
90
+
91
+ # @api private
92
+ def location_default_path(action, path)
93
+ case action
94
+ when :list, :create
95
+ path
96
+ when :read, :update, :delete
97
+ "#{path}/:id"
98
+ end
99
+ end
100
+ end
101
+
102
+ # Return URL for this resource. Resource if will be appended
103
+ # as suffix if present.
104
+ #
105
+ # @example
106
+ # user.new.url # => "http://users.srv.org/users"
107
+ #
108
+ # user = User.find 5
109
+ # Acfs.run
110
+ # user.url # => "http://users.srv.org/users/5"
111
+ #
112
+ # @return [ String ] Generated URL.
113
+ # @see ClassMethods#url
114
+ #
115
+ def url(**opts)
116
+ return nil if need_primary_key? && !primary_key?
117
+
118
+ self.class.service
119
+ .location(self.class, **opts, action: :read)
120
+ .build(attributes).str
121
+ end
122
+
123
+ # @api private
124
+ # Return true if resource needs a primary key (id) for singular actions.
125
+ def need_primary_key?
126
+ true
127
+ end
128
+
129
+ # @api private
130
+ # Return true if resource has a primary key (id) set.
131
+ def primary_key?
132
+ respond_to?(:id) && !id.nil?
133
+ end
134
+ end
135
+ end