acfs 0.16.0 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.travis.yml +5 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +13 -5
- data/Guardfile +6 -16
- data/README.md +43 -3
- data/Rakefile +11 -1
- data/acfs.gemspec +7 -6
- data/gemfiles/Gemfile.rails-3-0 +8 -0
- data/gemfiles/Gemfile.rails-3-1 +8 -0
- data/lib/acfs.rb +7 -0
- data/lib/acfs/configuration.rb +52 -1
- data/lib/acfs/global.rb +14 -0
- data/lib/acfs/messaging/client.rb +39 -0
- data/lib/acfs/messaging/message.rb +7 -0
- data/lib/acfs/messaging/receiver.rb +119 -0
- data/lib/acfs/model.rb +3 -0
- data/lib/acfs/model/attributes.rb +97 -12
- data/lib/acfs/model/attributes/boolean.rb +11 -1
- data/lib/acfs/model/attributes/integer.rb +11 -1
- data/lib/acfs/model/attributes/string.rb +11 -1
- data/lib/acfs/model/dirty.rb +14 -4
- data/lib/acfs/model/initialization.rb +7 -2
- data/lib/acfs/model/loadable.rb +16 -0
- data/lib/acfs/model/locatable.rb +25 -4
- data/lib/acfs/model/operational.rb +4 -0
- data/lib/acfs/model/persistence.rb +73 -14
- data/lib/acfs/model/query_methods.rb +44 -7
- data/lib/acfs/model/service.rb +23 -11
- data/lib/acfs/operation.rb +2 -0
- data/lib/acfs/runner.rb +3 -0
- data/lib/acfs/service.rb +38 -5
- data/lib/acfs/service/middleware.rb +23 -5
- data/lib/acfs/version.rb +1 -1
- data/lib/acfs/yard.rb +5 -0
- data/rubydoc.png +0 -0
- data/spec/acfs/configuration_spec.rb +0 -1
- data/spec/acfs/messaging/receiver_spec.rb +55 -0
- data/spec/acfs/model/attributes_spec.rb +4 -4
- data/spec/acfs/stub_spec.rb +1 -1
- data/spec/acfs_messaging_spec.rb +5 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/service.rb +12 -1
- metadata +35 -9
@@ -4,8 +4,11 @@ module Acfs::Model
|
|
4
4
|
#
|
5
5
|
module Initialization
|
6
6
|
|
7
|
-
#
|
7
|
+
# @api public
|
8
8
|
#
|
9
|
+
# Initializes a new model with the given `params`.
|
10
|
+
#
|
11
|
+
# @example
|
9
12
|
# class User
|
10
13
|
# include Acfs::Model
|
11
14
|
# attribute :name
|
@@ -18,7 +21,9 @@ module Acfs::Model
|
|
18
21
|
# user.email # => "bob@dom.tld"
|
19
22
|
# user.age # => 18
|
20
23
|
#
|
21
|
-
|
24
|
+
# @param [ Hash{ Symbol => Object } ] params Attributes to set on resource.
|
25
|
+
#
|
26
|
+
def initialize(params = {})
|
22
27
|
params.each do |attr, value|
|
23
28
|
self.public_send("#{attr}=", value)
|
24
29
|
end if params
|
data/lib/acfs/model/loadable.rb
CHANGED
@@ -1,14 +1,30 @@
|
|
1
1
|
module Acfs::Model
|
2
2
|
|
3
|
+
# Provides method to check for loading state of resources.
|
4
|
+
# A resource that is created but not yet fetched will be loaded
|
5
|
+
# after running {Acfs::Global#run Acfs.run}.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# user = User.find 5
|
9
|
+
# user.loaded? # => false
|
10
|
+
# Acfs.run
|
11
|
+
# user.loaded? # => true
|
12
|
+
#
|
3
13
|
module Loadable
|
4
14
|
extend ActiveSupport::Concern
|
5
15
|
|
16
|
+
# @api public
|
17
|
+
#
|
6
18
|
# Check if model is loaded or if request is still queued.
|
7
19
|
#
|
20
|
+
# @return [ Boolean ] True if resource is loaded, false otherwise.
|
21
|
+
#
|
8
22
|
def loaded?
|
9
23
|
!!@loaded
|
10
24
|
end
|
11
25
|
|
26
|
+
# @api private
|
27
|
+
#
|
12
28
|
# Mark model as loaded.
|
13
29
|
#
|
14
30
|
def loaded!
|
data/lib/acfs/model/locatable.rb
CHANGED
@@ -2,25 +2,46 @@ module Acfs::Model
|
|
2
2
|
|
3
3
|
# Provide methods for generation URLs for resources.
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# @example
|
6
6
|
# class User
|
7
7
|
# service AccountService # With base URL `http://acc.svr`
|
8
8
|
# end
|
9
|
-
# User.url
|
10
|
-
# User.url(5)
|
9
|
+
# User.url # => "http://acc.svr/users"
|
10
|
+
# User.url(5) # => "http://acc.svr/users/5"
|
11
11
|
#
|
12
12
|
module Locatable
|
13
13
|
extend ActiveSupport::Concern
|
14
14
|
|
15
15
|
module ClassMethods
|
16
16
|
|
17
|
-
# Return URL for this resource.
|
17
|
+
# Return URL for this class of resource. Given suffix will be appended.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# User.url # => "http://users.srv.org/users"
|
21
|
+
# User.url(5) # => "http://users.srv.org/users/5"
|
22
|
+
#
|
23
|
+
# @param [ String ] suffix Suffix to append to URL.
|
24
|
+
# @return [ String ] Generated URL.
|
25
|
+
# @see Acfs::Service#url_for Delegates to Service#url_for with `suffix` option.
|
18
26
|
#
|
19
27
|
def url(suffix = nil)
|
20
28
|
service.url_for(self, suffix: suffix)
|
21
29
|
end
|
22
30
|
end
|
23
31
|
|
32
|
+
# Return URL for this resource. Resource if will be appended
|
33
|
+
# as suffix if present.
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# user.new.url # => "http://users.srv.org/users"
|
37
|
+
#
|
38
|
+
# user = User.find 5
|
39
|
+
# Acfs.run
|
40
|
+
# user.url # => "http://users.srv.org/users/5"
|
41
|
+
#
|
42
|
+
# @return [ String ] Generated URL.
|
43
|
+
# @see ClassMethods#url
|
44
|
+
#
|
24
45
|
def url
|
25
46
|
return nil if id.nil?
|
26
47
|
self.class.service.url_for self, suffix: read_attribute(:id)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Acfs::Model
|
2
2
|
|
3
|
+
# @api private
|
4
|
+
#
|
3
5
|
# Provide methods for creating and processing CRUD operations and
|
4
6
|
# handling responses. That includes error handling as well as
|
5
7
|
# handling stubbed resources.
|
@@ -11,6 +13,8 @@ module Acfs::Model
|
|
11
13
|
delegate :operation, to: :'self.class'
|
12
14
|
|
13
15
|
module ClassMethods
|
16
|
+
|
17
|
+
# Invoke CRUD operation.
|
14
18
|
def operation(action, opts = {}, &block)
|
15
19
|
Acfs.runner.process ::Acfs::Operation.new self, action, opts, &block
|
16
20
|
end
|
@@ -6,39 +6,54 @@ module Acfs
|
|
6
6
|
module Persistence
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
|
+
# @api public
|
10
|
+
#
|
9
11
|
# Check if the model is persisted. A model is persisted if
|
10
12
|
# it is saved after beeing created or when it was not changed
|
11
13
|
# since it was loaded.
|
12
14
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
15
|
+
# @example Newly created resource:
|
16
|
+
# user = User.new name: "John"
|
17
|
+
# user.persisted? # => false
|
18
|
+
# user.save
|
19
|
+
# user.persisted? # => true
|
20
|
+
#
|
21
|
+
# @example Modified resource:
|
22
|
+
# user2 = User.find 5
|
23
|
+
# user2.persisted? # => true
|
24
|
+
# user2.name = 'Amy'
|
25
|
+
# user2.persisted? # => false
|
26
|
+
# user2.save
|
27
|
+
# user2.persisted? # => true
|
17
28
|
#
|
18
|
-
#
|
19
|
-
# user2.persisted? # => true
|
20
|
-
# user2.name = 'Amy'
|
21
|
-
# user2.persisted? # => false
|
22
|
-
# user2.save
|
23
|
-
# user2.persisted? # => true
|
29
|
+
# @return [ Boolean ] True if resource has no changes and is not newly created, false otherwise.
|
24
30
|
#
|
25
31
|
def persisted?
|
26
32
|
!new? && !changed?
|
27
33
|
end
|
28
34
|
|
35
|
+
# @api public
|
36
|
+
#
|
29
37
|
# Return true if model is a new record and was not saved yet.
|
30
38
|
#
|
39
|
+
# @return [ Boolean ] True if resource is newly created, false otherwise.
|
40
|
+
#
|
31
41
|
def new?
|
32
42
|
read_attribute(:id).nil?
|
33
43
|
end
|
34
44
|
alias :new_record? :new?
|
35
45
|
|
36
|
-
#
|
46
|
+
# @api public
|
47
|
+
#
|
48
|
+
# Saves the resource.
|
37
49
|
#
|
38
|
-
# It will
|
50
|
+
# It will PUT to the service to update the resource or send
|
39
51
|
# a POST to create a new one if the resource is new.
|
40
52
|
#
|
41
|
-
#
|
53
|
+
# Saving a resource is a synchronous operation.
|
54
|
+
#
|
55
|
+
# @return [ Boolean ] True if save operation was successful, false otherwise.
|
56
|
+
# @see #save! See #save! for available options.
|
42
57
|
#
|
43
58
|
def save(*args)
|
44
59
|
save! *args
|
@@ -47,7 +62,23 @@ module Acfs
|
|
47
62
|
false
|
48
63
|
end
|
49
64
|
|
50
|
-
|
65
|
+
# @api public
|
66
|
+
#
|
67
|
+
# Saves the resource. Raises an error if something happens.
|
68
|
+
#
|
69
|
+
# Saving a resource is a synchronous operation.
|
70
|
+
#
|
71
|
+
# @param [ Hash ] opts Hash with additional options.
|
72
|
+
# @option opts [ Hash ] :data Data to send to remote service. Default will be resource attributes.
|
73
|
+
#
|
74
|
+
# @raise [ Acfs::InvalidResource ]
|
75
|
+
# If remote services respond with 422 response. Will fill errors with data from response
|
76
|
+
# @raise [ Acfs::ErroneousResponse ]
|
77
|
+
# If remote service respond with not successful response.
|
78
|
+
#
|
79
|
+
# @see #save
|
80
|
+
#
|
81
|
+
def save!(opts = {})
|
51
82
|
#raise ::Acfs::InvalidResource errors: errors.to_a unless valid?
|
52
83
|
|
53
84
|
opts[:data] = attributes unless opts[:data]
|
@@ -59,19 +90,47 @@ module Acfs
|
|
59
90
|
|
60
91
|
module ClassMethods
|
61
92
|
|
93
|
+
# @api public
|
94
|
+
#
|
62
95
|
# Create a new resource sending given data. If resource cannot be
|
63
96
|
# created an error will be thrown.
|
64
97
|
#
|
98
|
+
# Saving a resource is a synchronous operation.
|
99
|
+
#
|
100
|
+
# @param [ Hash{ Symbol, String => Object }] data Data to send in create request.
|
101
|
+
# @return [ self ] Newly resource object.
|
102
|
+
#
|
103
|
+
# @raise [ Acfs::InvalidResource ]
|
104
|
+
# If remote services respond with 422 response. Will fill errors with data from response
|
105
|
+
# @raise [ Acfs::ErroneousResponse ]
|
106
|
+
# If remote service respond with not successful response.
|
107
|
+
#
|
108
|
+
# @see Acfs::Model::Persistence#save! Available options. `:data` will be overridden with provided data hash.
|
109
|
+
# @see #create
|
110
|
+
#
|
65
111
|
def create!(data, opts = {})
|
66
112
|
new.tap do |model|
|
67
113
|
model.save! opts.merge data: data
|
68
114
|
end
|
69
115
|
end
|
70
116
|
|
117
|
+
# @api public
|
118
|
+
#
|
71
119
|
# Create a new resource sending given data. If resource cannot be
|
72
120
|
# create model will be returned and error hash contains response
|
73
121
|
# errors if available.
|
74
122
|
#
|
123
|
+
# Saving a resource is a synchronous operation.
|
124
|
+
#
|
125
|
+
# @param [ Hash{ Symbol, String => Object }] data Data to send in create request.
|
126
|
+
# @return [ self ] Newly resource object.
|
127
|
+
#
|
128
|
+
# @raise [ Acfs::ErroneousResponse ]
|
129
|
+
# If remote service respond with not successful response.
|
130
|
+
#
|
131
|
+
# @see Acfs::Model::Persistence#save! Available options. `:data` will be overridden with provided data hash.
|
132
|
+
# @see #create!
|
133
|
+
#
|
75
134
|
def create(data, opts = {})
|
76
135
|
model = new
|
77
136
|
model.save! opts.merge data: data
|
@@ -2,7 +2,7 @@ module Acfs::Model
|
|
2
2
|
|
3
3
|
# Methods providing the query interface for finding resouces.
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# @example
|
6
6
|
# class MyUser
|
7
7
|
# include Acfs::Model
|
8
8
|
# end
|
@@ -18,13 +18,41 @@ module Acfs::Model
|
|
18
18
|
|
19
19
|
module ClassMethods
|
20
20
|
|
21
|
-
#
|
21
|
+
# @api public
|
22
22
|
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
23
|
+
# @overload find(id, opts = {})
|
24
|
+
# Find a single resource by given ID.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# user = User.find(5) # Will query `http://base.url/users/5`
|
28
|
+
#
|
29
|
+
# @param [ Fixnum ] id Resource IDs to fetch from remote service.
|
30
|
+
# @param [ Hash ] opts Additional options.
|
31
|
+
# @option opts [ Hash ] :params Additional parameters added to request. `:id` will be overridden
|
32
|
+
# with given ID.
|
33
|
+
#
|
34
|
+
# @yield [ resource ] Callback block to be executed after resource was fetched successfully.
|
35
|
+
# @yieldparam resource [ self ] Fetched resources.
|
36
|
+
#
|
37
|
+
# @return [ self ] Resource object if only one ID was given.
|
38
|
+
#
|
39
|
+
# @overload find(*ids, opts = {})
|
40
|
+
# Load collection of specified resources by given IDs.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# User.find(1, 2, 5) # Will return collection and will request
|
44
|
+
# # `http://base.url/users/1`, `http://base.url/users/2`
|
45
|
+
# # and `http://base.url/users/5` parallel
|
46
|
+
#
|
47
|
+
# @param [ Fixnum, ... ] ids One or more resource IDs to fetch from remote service.
|
48
|
+
# @param [ Hash ] opts Additional options.
|
49
|
+
# @option opts [ Hash ] :params Additional parameters added to request. `:id` will be overridden
|
50
|
+
# with individual resource ID.
|
51
|
+
#
|
52
|
+
# @yield [ collection ] Callback block to be executed after collection was fetched successfully.
|
53
|
+
# @yieldparam resource [ Collection ] Collection with fetched resources.
|
54
|
+
#
|
55
|
+
# @return [ Collection ] Collection of requested resources if multiple IDs were given.
|
28
56
|
#
|
29
57
|
def find(*attrs, &block)
|
30
58
|
opts = attrs.extract_options!
|
@@ -32,8 +60,17 @@ module Acfs::Model
|
|
32
60
|
attrs.size > 1 ? find_multiple(attrs, opts, &block) : find_single(attrs[0], opts, &block)
|
33
61
|
end
|
34
62
|
|
63
|
+
# @api public
|
64
|
+
#
|
35
65
|
# Try to load all resources.
|
36
66
|
#
|
67
|
+
# @param [ Hash ] params Request parameters that will be send to remote service.
|
68
|
+
#
|
69
|
+
# @yield [ collection ] Callback block to be executed when resource collection was loaded successfully.
|
70
|
+
# @yieldparam collection [ Collection ] Collection of fetched resources.
|
71
|
+
#
|
72
|
+
# @return [ Collection ] Collection of requested resources.
|
73
|
+
#
|
37
74
|
def all(params = {}, &block)
|
38
75
|
collection = ::Acfs::Collection.new
|
39
76
|
|
data/lib/acfs/model/service.rb
CHANGED
@@ -1,24 +1,36 @@
|
|
1
1
|
module Acfs::Model
|
2
2
|
|
3
|
-
# Included by Acfs::Model. Allows
|
3
|
+
# Included by Acfs::Model. Allows to configure the service
|
4
|
+
# a resource belongs to.
|
4
5
|
#
|
5
6
|
module Service
|
6
7
|
extend ActiveSupport::Concern
|
7
8
|
|
8
9
|
module ClassMethods
|
9
10
|
|
10
|
-
#
|
11
|
-
# are fetched from service. Return assigned service if no arguments are given.
|
11
|
+
# @api public
|
12
12
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
# self.base_url = 'http://acc.serv.org'
|
16
|
-
# end
|
13
|
+
# @overload service()
|
14
|
+
# Return service instance.
|
17
15
|
#
|
18
|
-
# class
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
16
|
+
# @return [ Service ] Service class instance.
|
17
|
+
#
|
18
|
+
# @overload service(klass, options = {})
|
19
|
+
# Link to service this model belongs to. Connection settings like base URL
|
20
|
+
# are fetched from service. Return assigned service if no arguments are given.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# class AccountService < Acfs::Client
|
24
|
+
# self.base_url = 'http://acc.serv.org'
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# class MyUser
|
28
|
+
# service AccountService
|
29
|
+
# end
|
30
|
+
# MyUser.find 5 # Will fetch `http://acc.serv.org/users/5`
|
31
|
+
#
|
32
|
+
# @param [ Class ] klass Service class derived from {Acfs::Service}.
|
33
|
+
# @param [ Object ] options Option delegated to service class initializer.
|
22
34
|
#
|
23
35
|
def service(klass = nil, options = {})
|
24
36
|
return @service unless klass
|
data/lib/acfs/operation.rb
CHANGED
data/lib/acfs/runner.rb
CHANGED
@@ -2,6 +2,8 @@ require 'acfs/service/middleware'
|
|
2
2
|
|
3
3
|
module Acfs
|
4
4
|
|
5
|
+
# @api private
|
6
|
+
#
|
5
7
|
class Runner
|
6
8
|
include Service::Middleware
|
7
9
|
attr_reader :adapter
|
@@ -59,6 +61,7 @@ module Acfs
|
|
59
61
|
def clear
|
60
62
|
queue.clear
|
61
63
|
adapter.abort
|
64
|
+
@running = false
|
62
65
|
end
|
63
66
|
|
64
67
|
private
|
data/lib/acfs/service.rb
CHANGED
@@ -2,21 +2,38 @@ require 'acfs/service/middleware'
|
|
2
2
|
|
3
3
|
module Acfs
|
4
4
|
|
5
|
-
# Service
|
5
|
+
# User {Acfs::Service} to define your services. That includes
|
6
|
+
# an identity used to identify the service in configuration files
|
7
|
+
# and middlewares the service uses.
|
8
|
+
#
|
9
|
+
# Configure your service URLs in a YAML file loaded in an
|
10
|
+
# initializer using the identity as a key:
|
11
|
+
#
|
12
|
+
# production:
|
13
|
+
# services:
|
14
|
+
# user_service_key: "http://users.service.org/base/path"
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# class UserService < Acfs::Service
|
18
|
+
# identity :user_service_key
|
19
|
+
#
|
20
|
+
# use Acfs::Middleware::MessagePackDecoder
|
21
|
+
# end
|
6
22
|
#
|
7
23
|
class Service
|
8
24
|
attr_accessor :options
|
9
25
|
|
10
26
|
include Service::Middleware
|
11
27
|
|
28
|
+
# @api private
|
29
|
+
#
|
12
30
|
def initialize(options = {})
|
13
31
|
@options = options
|
14
32
|
end
|
15
33
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
34
|
+
# @api private
|
35
|
+
# @return [String]
|
36
|
+
#
|
20
37
|
def url_for(resource_class, options = {})
|
21
38
|
options.reverse_merge! self.options
|
22
39
|
|
@@ -28,11 +45,27 @@ module Acfs
|
|
28
45
|
|
29
46
|
class << self
|
30
47
|
|
48
|
+
# @api public
|
49
|
+
#
|
50
|
+
# @overload identity()
|
51
|
+
# Return configured identity key or derive key from class name.
|
52
|
+
#
|
53
|
+
# @return [Symbol] Service identity key.
|
54
|
+
#
|
55
|
+
# @overload identity(identity)
|
56
|
+
# Set identity key.
|
57
|
+
#
|
58
|
+
# @param [#to_s] identity New identity key.
|
59
|
+
# @return [Symbol] New set identity key.
|
60
|
+
#
|
31
61
|
def identity(identity = nil)
|
32
62
|
@identity = identity.to_s.to_sym unless identity.nil?
|
33
63
|
@identity ||= name.to_sym
|
34
64
|
end
|
35
65
|
|
66
|
+
# @api private
|
67
|
+
# @return [String]
|
68
|
+
#
|
36
69
|
def base_url
|
37
70
|
unless (base = Acfs::Configuration.current.locate identity)
|
38
71
|
raise ArgumentError, "#{identity} not configured. Add `locate '#{identity.to_s.underscore}', 'http://service.url/'` to your configuration."
|