acfs 0.16.0 → 0.17.0

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