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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0f5f03951ec6374b76946211e390bfc2c0ff6f1f
4
- data.tar.gz: af8cf339ce2bc9e4a5542a55bb1b8d1e8b63793b
3
+ metadata.gz: 879fbc544d14669fd0db86a7705c1ff10e997a56
4
+ data.tar.gz: 6424e9a7fcc978e739e80a0ee009fa141ce2ad25
5
5
  SHA512:
6
- metadata.gz: 66728dbe0550a7d8585d1d47b615c60534417d176125ddca78b572e11e13f6611b4ec16877db10096c4752f9d24f1323ea01da8bfc5f0b97fd9bcd83e530a85f
7
- data.tar.gz: 55f211e28b344c2c5245161d76c12826cd6c89d36132a7838fdfa1402ac5e1dde1618851e3a1e9d6c1c40b5b17ff6bbdef5c6101b464d1cd7196a46d66418a3c
6
+ metadata.gz: 697f3c7d31e1aa7081eb0e471e1065dcb2a9ec0be7e57b0f9f130cea0c40bb96dba91bbbb9faaf30eaec0aac29de99ee5b27df8b278696d7d43a9c8d1d4e2826
7
+ data.tar.gz: da505f1e0fcf03d21f594d81f32527dbe0b991dea6b310a400c0d5182cd573a9f3bfabeb3fddd5438a69dee344ef0dcbd540bc8994ee61cadf92e04a3238f436
data/.gitignore CHANGED
@@ -4,7 +4,8 @@
4
4
  .bundle
5
5
  .config
6
6
  .yardoc
7
- Gemfile.lock
7
+ doc
8
+ Gemfile*.lock
8
9
  InstalledFiles
9
10
  _yardoc
10
11
  coverage
@@ -1,4 +1,5 @@
1
1
  language: ruby
2
+ bundler_args: --without development
2
3
  rvm:
3
4
  - 2.0.0
4
5
  - 1.9.3
@@ -6,5 +7,9 @@ rvm:
6
7
  - rbx-19mode
7
8
 
8
9
  gemfile:
10
+ - gemfiles/Gemfile.rails-3-1
9
11
  - gemfiles/Gemfile.rails-3-2
10
12
  - gemfiles/Gemfile.rails-4-0
13
+
14
+ services:
15
+ - rabbitmq
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.0
4
+
5
+ * Basic messaging
6
+ * Extensible YARD documentation
7
+
3
8
  ## 0.16.0
4
9
 
5
10
  * Add YAML configuration
data/Gemfile CHANGED
@@ -1,19 +1,27 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in acfs.gemspec
4
- gemroot = File.dirname File.absolute_path __FILE__
5
- gemspec path: gemroot
6
-
7
3
  # Development gems
8
4
  #
9
5
  gem 'webmock', '~> 1.7'
10
6
  gem 'rake'
11
7
  gem 'rspec'
12
- gem 'guard-rspec'
13
8
  gem 'coveralls'
9
+ gem 'json', '~> 1.7.7'
14
10
 
11
+ # Doc
12
+ group :development do
13
+ gem 'yard', '~> 0.8.6'
14
+ gem 'listen'
15
+ gem 'guard-yard'
16
+ gem 'guard-rspec'
17
+ gem 'redcarpet', platform: :ruby
18
+ end
15
19
 
16
20
  # Platform specific development dependencies
17
21
  #
18
22
  gem 'msgpack', platform: :ruby
19
23
  gem 'msgpack-jruby', require: 'msgpack', platform: :jruby
24
+
25
+ # Specify your gem's dependencies in acfs.gemspec
26
+ gemroot = File.dirname File.absolute_path __FILE__
27
+ gemspec path: gemroot
data/Guardfile CHANGED
@@ -3,22 +3,12 @@
3
3
 
4
4
  guard 'rspec' do
5
5
  watch(%r{^spec/.+_spec\.rb$})
6
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
- watch('spec/spec_helper.rb') { "spec" }
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
8
7
 
9
- # Rails example
10
- watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
11
- watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
12
- watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
13
- watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
14
- watch('config/routes.rb') { "spec/routing" }
15
- watch('app/controllers/application_controller.rb') { "spec/controllers" }
16
-
17
- # Capybara features specs
18
- watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
19
-
20
- # Turnip features and steps
21
- watch(%r{^spec/acceptance/(.+)\.feature$})
22
- watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
8
+ watch('spec/spec_helper.rb') { 'spec' }
9
+ watch(%r{^spec/support/(.+)\.rb$}) { 'spec' }
23
10
  end
24
11
 
12
+ guard 'yard' do
13
+ watch(%r{lib/.+\.rb})
14
+ end
data/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Acfs - *API client for services*
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/acfs.png)](http://badge.fury.io/rb/acfs) [![Build Status](https://travis-ci.org/jgraichen/acfs.png?branch=master)](https://travis-ci.org/jgraichen/acfs) [![Coverage Status](https://coveralls.io/repos/jgraichen/acfs/badge.png?branch=master)](https://coveralls.io/r/jgraichen/acfs) [![Code Climate](https://codeclimate.com/github/jgraichen/acfs.png)](https://codeclimate.com/github/jgraichen/acfs) [![Dependency Status](https://gemnasium.com/jgraichen/acfs.png)](https://gemnasium.com/jgraichen/acfs)
3
+ [![Gem Version](https://badge.fury.io/rb/acfs.png)](http://badge.fury.io/rb/acfs)
4
+ [![Build Status](https://travis-ci.org/jgraichen/acfs.png?branch=master)](https://travis-ci.org/jgraichen/acfs)
5
+ [![Coverage Status](https://coveralls.io/repos/jgraichen/acfs/badge.png?branch=master)](https://coveralls.io/r/jgraichen/acfs)
6
+ [![Code Climate](https://codeclimate.com/github/jgraichen/acfs.png)](https://codeclimate.com/github/jgraichen/acfs)
7
+ [![Dependency Status](https://gemnasium.com/jgraichen/acfs.png)](https://gemnasium.com/jgraichen/acfs)
8
+ [![RubyDoc Documentation](https://raw.github.com/jgraichen/acfs/master/rubydoc.png)](http://rubydoc.info/github/jgraichen/acfs/master/frames)
4
9
 
5
10
  Acfs is a library to develop API client libraries for single services within a larger service oriented application.
6
11
 
@@ -16,11 +21,11 @@ Add this line to your application's Gemfile:
16
21
 
17
22
  And then execute:
18
23
 
19
- $ bundle
24
+ > bundle
20
25
 
21
26
  Or install it yourself as:
22
27
 
23
- $ gem install acfs
28
+ > gem install acfs
24
29
 
25
30
  ## Usage
26
31
 
@@ -185,6 +190,38 @@ it 'should find user number one' do
185
190
  end
186
191
  ```
187
192
 
193
+ ## Messaging (Experimental)
194
+
195
+ Acfs 0.17.0 includes experimental messaging support using RabbitMQ. You can create receivers for messages as well as send custom messages. Messages can be high level structures (like hashes) and will be packed into MessagePack.
196
+
197
+ Create your custom receivers e.g. in `app/receivers`:
198
+
199
+ ```ruby
200
+ # app/receivers/my_receiver.rb
201
+
202
+ class MyReceiver
203
+ include Acfs::Messaging::Receiver
204
+ route "my.#" # Specify routing key for receiving messages
205
+
206
+ def receive(delivery_info, metadata, payload)
207
+ puts payload.inspect
208
+ # more foo...
209
+ end
210
+ end
211
+ ```
212
+
213
+ Make sure the receivers get loaded by placing an initializer in you app that require all receivers. You need to do this manually but it will be automated in some future release.
214
+
215
+ You can now send messages by calling the `publish` method directly:
216
+
217
+ ```ruby
218
+ Acfs::Messaging::Client.instance.publish "my.message", { message: "Hi!" }
219
+ ```
220
+
221
+ This will invoke `#receive` on an instance of your receiver class.
222
+
223
+ Be aware that messaging is still experimental and *will* change in future releases.
224
+
188
225
  ## Roadmap
189
226
 
190
227
  * Update
@@ -201,6 +238,9 @@ end
201
238
  Modified Headers, conflict detection, ...
202
239
  * Pagination? Filtering? (If service API provides such features.)
203
240
  * Messaging Queue support for services and models
241
+ * Allow triggering messages on resource events (CRUD)
242
+ * Abstract messages into objects
243
+ * Middleware stack for messages?
204
244
  * Documentation
205
245
 
206
246
  ## Contributing
data/Rakefile CHANGED
@@ -2,5 +2,15 @@ require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
-
6
5
  task :default => :spec
6
+
7
+ begin
8
+ require 'yard'
9
+ require 'yard/rake/yardoc_task'
10
+
11
+ YARD::Rake::YardocTask.new do |t|
12
+ t.files = %w(lib/**/*.rb)
13
+ t.options = %w(--output-dir doc/)
14
+ end
15
+ rescue LoadError
16
+ end
@@ -4,25 +4,26 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'acfs/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "acfs"
7
+ spec.name = 'acfs'
8
8
  spec.version = Acfs::VERSION
9
9
  spec.authors = ['Jan Graichen']
10
10
  spec.email = %w(jg@altimos.de)
11
11
  spec.description = %q{API Client For Services}
12
12
  spec.summary = %q{An abstract API base client for service oriented application.}
13
- spec.homepage = ""
14
- spec.license = "MIT"
13
+ spec.homepage = 'https://github.com/jgraichen/acfs'
14
+ spec.license = 'MIT'
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = %w(lib)
20
20
 
21
- spec.add_runtime_dependency 'activesupport'
22
- spec.add_runtime_dependency 'activemodel'
23
- spec.add_runtime_dependency 'actionpack'
21
+ spec.add_runtime_dependency 'activesupport', '>= 3.1'
22
+ spec.add_runtime_dependency 'activemodel', '>= 3.1'
23
+ spec.add_runtime_dependency 'actionpack', '>= 3.1'
24
24
  spec.add_runtime_dependency 'multi_json'
25
25
  spec.add_runtime_dependency 'typhoeus'
26
+ spec.add_runtime_dependency 'bunny', '~> 0.9.0.pre12'
26
27
  spec.add_runtime_dependency 'rack'
27
28
 
28
29
  spec.add_development_dependency 'bundler', '~> 1.3'
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Load root Gemfile
4
+ self.instance_eval Bundler.read_file 'Gemfile'
5
+
6
+ gem 'activesupport', '~> 3.0.0'
7
+ gem 'activemodel', '~> 3.0.0'
8
+ gem 'actionpack', '~> 3.0.0'
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Load root Gemfile
4
+ self.instance_eval Bundler.read_file 'Gemfile'
5
+
6
+ gem 'activesupport', '~> 3.1.0'
7
+ gem 'activemodel', '~> 3.1.0'
8
+ gem 'actionpack', '~> 3.1.0'
@@ -22,6 +22,13 @@ module Acfs
22
22
  autoload :Runner
23
23
  autoload :Configuration
24
24
 
25
+ module Messaging
26
+ extend ActiveSupport::Autoload
27
+
28
+ autoload :Client
29
+ autoload :Receiver
30
+ end
31
+
25
32
  module Middleware
26
33
  extend ActiveSupport::Autoload
27
34
 
@@ -3,15 +3,27 @@ require 'yaml'
3
3
 
4
4
  module Acfs
5
5
 
6
- #
6
+ # Acfs configuration is used to locate services and get their base URLs.
7
7
  #
8
8
  class Configuration
9
9
  attr_reader :locations
10
10
 
11
+ # @api private
11
12
  def initialize
12
13
  @locations = {}
13
14
  end
14
15
 
16
+ # @api public
17
+ #
18
+ # Configure using given block. If block accepts zero arguments
19
+ # bock will be evaluated in context of the configuration instance
20
+ # otherwise the configuration instance will be given as first arguments.
21
+ #
22
+ # @yield [configuration] Give configuration as arguments or evaluate block
23
+ # in context of configuration object.
24
+ # @yieldparam configuration [Configuration] Configuration object.
25
+ # @return [undefined]
26
+ #
15
27
  def configure(&block)
16
28
  if block.arity > 0
17
29
  block.call self
@@ -20,6 +32,21 @@ module Acfs
20
32
  end
21
33
  end
22
34
 
35
+ # @api public
36
+ #
37
+ # @overload locate(service, uri)
38
+ # Configures URL where a service can be reached.
39
+ #
40
+ # @param [Symbol] service Service identity key for service that is reachable under given URL.
41
+ # @param [String] uri URL where service is reachable. Will be passed to {URI.parse}.
42
+ # @return [undefined]
43
+ #
44
+ # @overload locate(service)
45
+ # Return configured base URL for given service identity key.
46
+ #
47
+ # @param [Symbol] service Service identity key to lookup.
48
+ # @return [URI, NilClass] Configured base URL or nil.
49
+ #
23
50
  def locate(service, uri = nil)
24
51
  service = service.to_s.underscore.to_sym
25
52
  if uri.nil?
@@ -29,6 +56,13 @@ module Acfs
29
56
  end
30
57
  end
31
58
 
59
+ # @api public
60
+ #
61
+ # Load configuration from given YAML file.
62
+ #
63
+ # @param [String] filename Path to YAML configuration file.
64
+ # @return [undefined]
65
+ #
32
66
  def load(filename)
33
67
  config = YAML::load File.read filename
34
68
  env = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
@@ -41,6 +75,10 @@ module Acfs
41
75
  end
42
76
  end
43
77
 
78
+ # @api private
79
+ #
80
+ # Load services from configuration YAML.
81
+ #
44
82
  def load_services(services)
45
83
  services.each do |service, data|
46
84
  if (val = data).is_a?(String) || (val = data['locate'])
@@ -51,10 +89,23 @@ module Acfs
51
89
 
52
90
  class << self
53
91
 
92
+ # @api private
93
+ #
94
+ # Return current configuration object.
95
+ #
96
+ # @return [Configuration]
97
+ #
54
98
  def current
55
99
  @configuration ||= new
56
100
  end
57
101
 
102
+ # @api private
103
+ #
104
+ # Swap configuration object with given new one. Must be a {Configuration} object.
105
+ #
106
+ # @param [Configuration] configuration
107
+ # @return [undefined]
108
+ #
58
109
  def set(configuration)
59
110
  @configuration = configuration if configuration.is_a? Configuration
60
111
  end
@@ -4,16 +4,30 @@ module Acfs
4
4
  #
5
5
  module Global
6
6
 
7
+ # @api private
8
+ # @return [Runner]
9
+ #
7
10
  def runner
8
11
  @runner ||= Runner.new Adapter::Typhoeus.new
9
12
  end
10
13
 
14
+ # @api public
15
+ #
11
16
  # Run all queued operations.
12
17
  #
18
+ # @return [undefined]
19
+ #
13
20
  def run
14
21
  runner.start
15
22
  end
16
23
 
24
+ # @api public
25
+ #
26
+ # Configure acfs using given block.
27
+ #
28
+ # @return [undefined]
29
+ # @see Configuration#configure
30
+ #
17
31
  def configure(&block)
18
32
  Configuration.current.configure &block
19
33
  end
@@ -0,0 +1,39 @@
1
+ require 'bunny'
2
+
3
+ module Acfs::Messaging
4
+
5
+ # @macro experimental
6
+ #
7
+ class Client
8
+
9
+ def initialize
10
+ @bunny ||= Bunny.new.tap do |bunny|
11
+ bunny.start
12
+ end
13
+ end
14
+
15
+ def channel
16
+ @channel ||= @bunny.create_channel
17
+ end
18
+
19
+ def exchange
20
+ @exchange ||= @channel.topic 'acfs-0.17.0-2', auto_delete: true
21
+ end
22
+
23
+ def publish(routing_key, message)
24
+ exchange.publish MessagePack.pack(message), routing_key: routing_key
25
+ end
26
+
27
+ class << self
28
+
29
+ def instance
30
+ @instance ||= new
31
+ end
32
+
33
+ def register(receiver)
34
+ @receivers ||= []
35
+ @receivers << receiver.instance.tap { |r| r.init instance }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module Acfs::Messaging
2
+
3
+ #
4
+ class Message
5
+
6
+ end
7
+ end