acfs 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +5 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Gemfile +13 -5
  6. data/Guardfile +6 -16
  7. data/README.md +43 -3
  8. data/Rakefile +11 -1
  9. data/acfs.gemspec +7 -6
  10. data/gemfiles/Gemfile.rails-3-0 +8 -0
  11. data/gemfiles/Gemfile.rails-3-1 +8 -0
  12. data/lib/acfs.rb +7 -0
  13. data/lib/acfs/configuration.rb +52 -1
  14. data/lib/acfs/global.rb +14 -0
  15. data/lib/acfs/messaging/client.rb +39 -0
  16. data/lib/acfs/messaging/message.rb +7 -0
  17. data/lib/acfs/messaging/receiver.rb +119 -0
  18. data/lib/acfs/model.rb +3 -0
  19. data/lib/acfs/model/attributes.rb +97 -12
  20. data/lib/acfs/model/attributes/boolean.rb +11 -1
  21. data/lib/acfs/model/attributes/integer.rb +11 -1
  22. data/lib/acfs/model/attributes/string.rb +11 -1
  23. data/lib/acfs/model/dirty.rb +14 -4
  24. data/lib/acfs/model/initialization.rb +7 -2
  25. data/lib/acfs/model/loadable.rb +16 -0
  26. data/lib/acfs/model/locatable.rb +25 -4
  27. data/lib/acfs/model/operational.rb +4 -0
  28. data/lib/acfs/model/persistence.rb +73 -14
  29. data/lib/acfs/model/query_methods.rb +44 -7
  30. data/lib/acfs/model/service.rb +23 -11
  31. data/lib/acfs/operation.rb +2 -0
  32. data/lib/acfs/runner.rb +3 -0
  33. data/lib/acfs/service.rb +38 -5
  34. data/lib/acfs/service/middleware.rb +23 -5
  35. data/lib/acfs/version.rb +1 -1
  36. data/lib/acfs/yard.rb +5 -0
  37. data/rubydoc.png +0 -0
  38. data/spec/acfs/configuration_spec.rb +0 -1
  39. data/spec/acfs/messaging/receiver_spec.rb +55 -0
  40. data/spec/acfs/model/attributes_spec.rb +4 -4
  41. data/spec/acfs/stub_spec.rb +1 -1
  42. data/spec/acfs_messaging_spec.rb +5 -0
  43. data/spec/spec_helper.rb +1 -1
  44. data/spec/support/service.rb +12 -1
  45. metadata +35 -9
@@ -4,8 +4,11 @@ module Acfs::Model
4
4
  #
5
5
  module Initialization
6
6
 
7
- # Initializes a new model with the given +params+.
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
- def initialize(params={})
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
@@ -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!
@@ -2,25 +2,46 @@ module Acfs::Model
2
2
 
3
3
  # Provide methods for generation URLs for resources.
4
4
  #
5
- # Example
5
+ # @example
6
6
  # class User
7
7
  # service AccountService # With base URL `http://acc.svr`
8
8
  # end
9
- # User.url #=> "http://acc.svr/users"
10
- # User.url(5) #=> "http://acc.svr/users/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
- # user = User.new name: "John"
14
- # user.persisted? # => false
15
- # user.save
16
- # user.persisted? # => true
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
- # user2 = User.find 5
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
- # Save the resource.
46
+ # @api public
47
+ #
48
+ # Saves the resource.
37
49
  #
38
- # It will PATCH to the service to update the resource or send
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
- # `#save` return true of operation was successful, otherwise false.
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
- def save!(opts = {}) # :nodoc:
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
- # Example
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
- # Try to load a resource by given id.
21
+ # @api public
22
22
  #
23
- # Example
24
- # User.find(5) # Will query `http://base.url/users/5`
25
- # User.find(1, 2, 5) # Will return collection and will query
26
- # # `http://base.url/users/1`, `http://base.url/users/2`
27
- # # and `http://base.url/users/5` parallel
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
 
@@ -1,24 +1,36 @@
1
1
  module Acfs::Model
2
2
 
3
- # Included by Acfs::Model. Allows a model to belong to a service.
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
- # Link to service this model belongs to. Connection settings like base URL
11
- # are fetched from service. Return assigned service if no arguments are given.
11
+ # @api public
12
12
  #
13
- # Example
14
- # class AccountService < Acfs::Client
15
- # self.base_url = 'http://acc.serv.org'
16
- # end
13
+ # @overload service()
14
+ # Return service instance.
17
15
  #
18
- # class MyUser
19
- # service AccountService
20
- # end
21
- # MyUser.find 5 # Will fetch `http://acc.serv.org/users/5`
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
@@ -1,5 +1,7 @@
1
1
  module Acfs
2
2
 
3
+ # @api private
4
+ #
3
5
  # Describes a CRUD operation. Handle request creation and response
4
6
  # processing as well as error handling and stubbing.
5
7
  #
@@ -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
@@ -2,21 +2,38 @@ require 'acfs/service/middleware'
2
2
 
3
3
  module Acfs
4
4
 
5
- # Service object.
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
- def options
17
- @options
18
- end
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."