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
@@ -0,0 +1,119 @@
1
+ module Acfs::Messaging
2
+
3
+ # @macro experimental
4
+ #
5
+ # A {Receiver} subscribes to a messaging queue and
6
+ # reacts to received messages.
7
+ #
8
+ # @example
9
+ # class UserWelcomeReceiver < Acfs::Receiver
10
+ #
11
+ module Receiver
12
+ extend ActiveSupport::Concern
13
+
14
+ included do
15
+ Acfs::Messaging::Client.register self
16
+ end
17
+
18
+ def init(client)
19
+ @channel = client.channel
20
+ @queue = @channel.queue self.class.queue, options
21
+ @queue.bind client.exchange, routing_key: self.class.routing_key
22
+
23
+ @queue.subscribe do |delivery_info, metadata, payload|
24
+ process_received delivery_info, metadata, payload
25
+ end
26
+ end
27
+
28
+ def process_received(delivery_info, metadata, payload)
29
+ return if delivery_info.nil?
30
+
31
+ payload = MessagePack.unpack payload
32
+ payload.symbolize_keys! if payload.is_a? Hash
33
+ receive delivery_info, metadata, payload
34
+ end
35
+
36
+ def options
37
+ @options ||= self.class.options
38
+ end
39
+
40
+ # @macro experimental
41
+ #
42
+ # Handle incoming messages. Should be overridden by derived class.
43
+ #
44
+ def receive(delivery_info, metadata, payload)
45
+
46
+ end
47
+
48
+ module ClassMethods
49
+
50
+ # @macro experimental
51
+ #
52
+ # @overload queue
53
+ # Return name of queue to listen on. Default name will be
54
+ # generated based on full class name.
55
+ #
56
+ # @return [String] Name of queue to listen on.
57
+ #
58
+ # @overload queue(name)
59
+ # Set queue name to listen on.
60
+ #
61
+ # @param [String] name Queue name to listen on.
62
+ # @return [String] Set name of queue to listen on.
63
+ #
64
+ def queue(*args)
65
+ raise ArgumentError.new 'Receiver.queue accepts zero or one argument.' if args.size > 1
66
+
67
+ @queue ||= self.name.underscore.gsub('/', '.')
68
+ @queue = args[0].nil? ? nil : args[0].to_s if args.size > 0
69
+ @queue
70
+ end
71
+
72
+ # @macro experimental
73
+ #
74
+ # Specify routing key for this receiver. The routing key defines
75
+ # which exchanges should be subscribed to receive messages from.
76
+ #
77
+ # @param [#to_s] key Routing key.
78
+ #
79
+ def route(key)
80
+ @routing_key = key.to_s
81
+ end
82
+
83
+ # @macro experimental
84
+ #
85
+ # @overload options
86
+ # Return configured options for this receiver.
87
+ #
88
+ # @return [Hash] Configured options.
89
+ #
90
+ # @overload options(opts)
91
+ # Set configuration options.
92
+ #
93
+ # @param [Hash] opts Messaging channel options.
94
+ # @return [Hash] Configured options.
95
+ #
96
+ def options(opts = nil)
97
+ @options ||= {}
98
+ return @options if opts.nil?
99
+
100
+ @options = opts.to_hash if opts.respond_to? :to_hash
101
+ end
102
+
103
+ # @api private
104
+ #
105
+ # Return configured routing key if any.
106
+ # Default value is `#`.
107
+ #
108
+ # @return [String, Nil] Routing key or `nil`.
109
+ #
110
+ def routing_key
111
+ @routing_key ||= '#'
112
+ end
113
+
114
+ def instance
115
+ @instance ||= new
116
+ end
117
+ end
118
+ end
119
+ end
@@ -11,6 +11,9 @@ require 'acfs/model/relations'
11
11
  require 'acfs/model/service'
12
12
 
13
13
  module Acfs
14
+
15
+ # @api public
16
+ #
14
17
  module Model
15
18
  extend ActiveSupport::Concern
16
19
 
@@ -1,9 +1,10 @@
1
1
  module Acfs::Model
2
2
 
3
- # == Acfs Attributes
3
+ # = Acfs Attributes
4
4
  #
5
5
  # Allows to specify attributes of a class with default values and type safety.
6
6
  #
7
+ # @example
7
8
  # class User
8
9
  # include Acfs::Model
9
10
  # attribute :name, :string, default: 'Anon'
@@ -18,13 +19,23 @@ module Acfs::Model
18
19
  extend ActiveSupport::Concern
19
20
  include ActiveModel::AttributeMethods
20
21
 
21
- def initialize(*attrs) # :nodoc:
22
+ # @api public
23
+ #
24
+ # Write default attributes defined in resource class.
25
+ #
26
+ # @see #write_attributes
27
+ # @see ClassMethods#attributes
28
+ #
29
+ def initialize(*attrs)
22
30
  self.write_attributes self.class.attributes, change: false
23
31
  super
24
32
  end
25
33
 
34
+ # @api public
35
+ #
26
36
  # Returns ActiveModel compatible list of attributes and values.
27
37
  #
38
+ # @example
28
39
  # class User
29
40
  # include Acfs::Model
30
41
  # attribute :name, type: String, default: 'Anon'
@@ -32,24 +43,58 @@ module Acfs::Model
32
43
  # user = User.new(name: 'John')
33
44
  # user.attributes # => { "name" => "John" }
34
45
  #
46
+ # @return [ HashWithIndifferentAccess{ Symbol => Object } ] Attributes and their values.
47
+ #
35
48
  def attributes
36
49
  HashWithIndifferentAccess.new self.class.attributes.keys.inject({}) { |h, k| h[k.to_sym] = public_send k; h }
37
50
  end
38
51
 
39
- # Update all attributes with given hash.
52
+ # @api public
53
+ #
54
+ # Update all attributes with given hash. Attribute values will be casted
55
+ # to defined attribute type.
56
+ #
57
+ # @example
58
+ # user.attributes = { :name => 'Adam' }
59
+ # user.name # => 'Adam'
60
+ #
61
+ # @param [ Hash{ String, Symbol => Object }, #each{|key, value|} ] attributes to set in resource.
62
+ # @see #write_attributes Delegates attributes hash to `#write_attributes`.
40
63
  #
41
64
  def attributes=(attributes)
42
65
  write_attributes attributes
43
66
  end
44
67
 
45
- # Read an attribute.
68
+ # @api public
69
+ #
70
+ # Read an attribute from instance variable.
71
+ #
72
+ # @param [ Symbol, String ] name Attribute name.
73
+ # @return [ Object ] Attribute value.
46
74
  #
47
75
  def read_attribute(name)
48
76
  instance_variable_get :"@#{name}"
49
77
  end
50
78
 
79
+ # @api public
80
+ #
51
81
  # Write a hash of attributes and values.
52
82
  #
83
+ # If attribute value is a `Proc` it will be evaluated in the context
84
+ # of the resource after all non-proc attribute values are set. Values
85
+ # will be casted to defined attribute type.
86
+ #
87
+ # The behavior is used to apply default attributes from resource
88
+ # class definition.
89
+ #
90
+ # @example
91
+ # user.write_attributes { :name => 'john', :email => lambda{ "#{name}@example.org" } }
92
+ # user.name # => 'john'
93
+ # user.email # => 'john@example.org'
94
+ #
95
+ # @param [ Hash{ String, Symbol => Object, Proc }, #each{|key, value|} ] attributes to write.
96
+ # @see #write_attribute Delegates attribute values to `#write_attribute`.
97
+ #
53
98
  def write_attributes(attributes, opts = {})
54
99
  return false unless attributes.respond_to? :each
55
100
 
@@ -66,37 +111,58 @@ module Acfs::Model
66
111
  procs.each do |key, proc|
67
112
  write_attribute key, instance_exec(&proc), opts
68
113
  end
114
+
69
115
  true
70
116
  end
71
117
 
72
- # Write an attribute.
118
+ # @api public
119
+ #
120
+ # Write single attribute with given value. Value will be casted
121
+ # to defined attribute type.
122
+ #
123
+ # @param [ String, Symbol ] name Attribute name.
124
+ # @param [ Object ] value Value to write.
125
+ # @raise [ ArgumentError ] If no attribute with given name is defined.
73
126
  #
74
127
  def write_attribute(name, value, opts = {})
75
128
  if (type = self.class.attribute_types[name.to_sym]).nil?
76
- raise "Unknown attribute `#{name}`."
129
+ raise ArgumentError.new "Unknown attribute `#{name}`."
77
130
  end
78
131
 
79
132
  write_raw_attribute name, value.nil? ? nil : type.cast(value), opts
80
133
  end
81
134
 
82
- # Write an attribute without checking type and existence or casting
83
- # value to attributes type.
135
+ # @api private
136
+ #
137
+ # Write an attribute without checking type or existence or casting
138
+ # value to attributes type. Value be stored in an instance variable
139
+ # named after attribute name.
140
+ #
141
+ # @param [ String, Symbol ] name Attribute name.
142
+ # @param [ Object ] value Attribute value.
84
143
  #
85
144
  def write_raw_attribute(name, value, _ = {})
86
145
  instance_variable_set :"@#{name}", value
87
146
  end
88
147
 
89
- module ClassMethods # :nodoc:
148
+ module ClassMethods
90
149
 
150
+ # @api public
151
+ #
91
152
  # Define a model attribute by name and type. Will create getter and
92
153
  # setter for given attribute name. Existing methods will be overridden.
93
154
  #
155
+ # Available types can be found in `Acfs::Model::Attributes::*`.
156
+ #
157
+ # @example
94
158
  # class User
95
159
  # include Acfs::Model
96
160
  # attribute :name, :string, default: 'Anon'
161
+ # attribute :email, :string, default: lambda{ "#{name}@example.org"}
97
162
  # end
98
163
  #
99
- # Available types can be found in `Acfs::Model::Attributes::*`.
164
+ # @param [ #to_sym ] name Attribute name.
165
+ # @param [ Symbol, String, Class ] type Attribute type identifier or type class.
100
166
  #
101
167
  def attribute(name, type, opts = {})
102
168
  if type.is_a? Symbol or type.is_a? String
@@ -106,8 +172,11 @@ module Acfs::Model
106
172
  define_attribute name.to_sym, type, opts
107
173
  end
108
174
 
175
+ # @api public
176
+ #
109
177
  # Return list of possible attributes and default values for this model class.
110
178
  #
179
+ # @example
111
180
  # class User
112
181
  # include Acfs::Model
113
182
  # attribute :name, :string
@@ -115,20 +184,36 @@ module Acfs::Model
115
184
  # end
116
185
  # User.attributes # => { "name": nil, "age": 25 }
117
186
  #
187
+ # @return [ Hash{ String => Object, Proc } ] Attributes with default values.
188
+ #
118
189
  def attributes
119
190
  @attributes ||= {}
120
191
  end
121
192
 
193
+ # @api public
194
+ #
122
195
  # Return hash of attributes and there types.
123
196
  #
197
+ # @example
198
+ # class User
199
+ # include Acfs::Model
200
+ # attribute :name, :string
201
+ # attribute :age, :integer, default: 25
202
+ # end
203
+ # User.attributes # => { "name": Acfs::Model::Attributes::String, "age": Acfs::Model::Attributes::Integer }
204
+ #
205
+ # @return [ Hash{ Symbol => Class } ] Attributes and their types.
206
+ #
124
207
  def attribute_types
125
208
  @attribute_types ||= {}
126
209
  end
127
210
 
128
211
  private
129
- def define_attribute(name, type, opts = {}) # :nodoc:
212
+ def define_attribute(name, type, opts = {})
213
+ name = name.to_s
130
214
  default_value = opts.has_key?(:default) ? opts[:default] : nil
131
215
  default_value = type.cast default_value unless default_value.is_a? Proc or default_value.nil?
216
+
132
217
  attributes[name] = default_value
133
218
  attribute_types[name.to_sym] = type
134
219
  define_attribute_method name
@@ -147,7 +232,7 @@ end
147
232
 
148
233
  # Load attribute type classes.
149
234
  #
150
- Dir[File.dirname(__FILE__) + "/attributes/*.rb"].sort.each do |path|
235
+ Dir[File.dirname(__FILE__) + '/attributes/*.rb'].sort.each do |path|
151
236
  filename = File.basename(path)
152
237
  require "acfs/model/attributes/#{filename}"
153
238
  end
@@ -1,8 +1,11 @@
1
1
  module Acfs::Model
2
2
  module Attributes
3
3
 
4
+ # @api public
5
+ #
4
6
  # Boolean attribute type. Use it in your model as an attribute type:
5
7
  #
8
+ # @example
6
9
  # class User
7
10
  # include Acfs::Model
8
11
  # attribute :name, :boolean
@@ -13,10 +16,17 @@ module Acfs::Model
13
16
  #
14
17
  # true, on, yes
15
18
  #
16
- module Boolean # :nodoc:
19
+ module Boolean
17
20
 
18
21
  TRUE_VALUES = %w(true on yes)
19
22
 
23
+ # @api public
24
+ #
25
+ # Cast given object to boolean.
26
+ #
27
+ # @param [Object] obj Object to cast.
28
+ # @return [TrueClass, FalseClass] Casted boolean.
29
+ #
20
30
  def self.cast(obj)
21
31
  return true if obj.is_a? TrueClass
22
32
  return false if obj.is_a? FalseClass
@@ -1,15 +1,25 @@
1
1
  module Acfs::Model
2
2
  module Attributes
3
3
 
4
+ # @api public
5
+ #
4
6
  # Integer attribute type. Use it in your model as an attribute type:
5
7
  #
8
+ # @example
6
9
  # class User
7
10
  # include Acfs::Model
8
11
  # attribute :name, :integer
9
12
  # end
10
13
  #
11
- module Integer # :nodoc:
14
+ module Integer
12
15
 
16
+ # @api public
17
+ #
18
+ # Cast given object to integer.
19
+ #
20
+ # @param [Object] obj Object to cast.
21
+ # @return [Fixnum] Casted object as fixnum.
22
+ #
13
23
  def self.cast(obj)
14
24
  obj.to_i
15
25
  end
@@ -1,15 +1,25 @@
1
1
  module Acfs::Model
2
2
  module Attributes
3
3
 
4
+ # @api public
5
+ #
4
6
  # String attribute type. Use it in your model as an attribute type:
5
7
  #
8
+ # @example
6
9
  # class User
7
10
  # include Acfs::Model
8
11
  # attribute :name, :string
9
12
  # end
10
13
  #
11
- module String # :nodoc:
14
+ module String
12
15
 
16
+ # @api public
17
+ #
18
+ # Cast given object to string.
19
+ #
20
+ # @param [Object] obj Object to cast.
21
+ # @return [String] Casted string.
22
+ #
13
23
  def self.cast(obj)
14
24
  obj.to_s
15
25
  end
@@ -7,12 +7,16 @@ module Acfs
7
7
  extend ActiveSupport::Concern
8
8
  include ActiveModel::Dirty
9
9
 
10
- # Resets all changes. Do not touch previous changes.
10
+ # @api private
11
+ #
12
+ # Resets all changes. Does not touch previous changes.
11
13
  #
12
14
  def reset_changes
13
15
  changed_attributes.clear
14
16
  end
15
17
 
18
+ # @api private
19
+ #
16
20
  # Save current changes as previous changes and reset
17
21
  # current one.
18
22
  #
@@ -21,15 +25,21 @@ module Acfs
21
25
  reset_changes
22
26
  end
23
27
 
24
- def save!(*) # :nodoc:
25
- super.tap { |__| swap_changes }
28
+ # @api private
29
+ #
30
+ def save!(*)
31
+ super.tap { |_| swap_changes }
26
32
  end
27
33
 
28
- def loaded! # :nodoc:
34
+ # @api private
35
+ #
36
+ def loaded!
29
37
  reset_changes
30
38
  super
31
39
  end
32
40
 
41
+ # @api private
42
+ #
33
43
  def write_raw_attribute(name, value, opts = {}) # :nodoc:
34
44
  attribute_will_change! name if opts[:change].nil? or opts[:change]
35
45
  super