sync_client 0.0.14

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 (66) hide show
  1. data/README.md +80 -0
  2. data/Rakefile +27 -0
  3. data/app/models/jobs/sync_client_jobs/publish.rb +12 -0
  4. data/app/models/sync_client/message.rb +15 -0
  5. data/app/models/sync_client/pub_message.rb +34 -0
  6. data/app/models/sync_client/service_resource/base.rb +27 -0
  7. data/app/models/sync_client/sub_message.rb +38 -0
  8. data/lib/generators/sync_client/install_generator.rb +24 -0
  9. data/lib/generators/templates/script +8 -0
  10. data/lib/generators/templates/sync_client.rb +13 -0
  11. data/lib/sync_client.rb +46 -0
  12. data/lib/sync_client/configurations/message_handler.rb +15 -0
  13. data/lib/sync_client/configurator.rb +53 -0
  14. data/lib/sync_client/configurators/message_handlers.rb +15 -0
  15. data/lib/sync_client/engine.rb +13 -0
  16. data/lib/sync_client/poller.rb +12 -0
  17. data/lib/sync_client/publisher.rb +52 -0
  18. data/lib/sync_client/queue_publisher.rb +28 -0
  19. data/lib/sync_client/sync_queue.rb +32 -0
  20. data/lib/sync_client/task_queue/delayed_job.rb +7 -0
  21. data/lib/sync_client/task_queue/inline_task_queue.rb +7 -0
  22. data/lib/sync_client/task_queue/resque.rb +7 -0
  23. data/lib/sync_client/tasks.rb +9 -0
  24. data/lib/sync_client/version.rb +3 -0
  25. data/lib/sync_client/worker.rb +15 -0
  26. data/test/app/pub_message_test.rb +33 -0
  27. data/test/app/service_resource/base_test.rb +18 -0
  28. data/test/app/sub_message_test.rb +42 -0
  29. data/test/dummy/README.rdoc +261 -0
  30. data/test/dummy/Rakefile +7 -0
  31. data/test/dummy/app/assets/javascripts/application.js +15 -0
  32. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  33. data/test/dummy/app/controllers/application_controller.rb +3 -0
  34. data/test/dummy/app/helpers/application_helper.rb +2 -0
  35. data/test/dummy/app/models/game.rb +17 -0
  36. data/test/dummy/app/models/player.rb +8 -0
  37. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  38. data/test/dummy/config.ru +4 -0
  39. data/test/dummy/config/application.rb +61 -0
  40. data/test/dummy/config/boot.rb +10 -0
  41. data/test/dummy/config/database.yml +25 -0
  42. data/test/dummy/config/environment.rb +5 -0
  43. data/test/dummy/config/environments/test.rb +37 -0
  44. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  45. data/test/dummy/config/initializers/inflections.rb +15 -0
  46. data/test/dummy/config/initializers/mime_types.rb +5 -0
  47. data/test/dummy/config/initializers/secret_token.rb +7 -0
  48. data/test/dummy/config/initializers/session_store.rb +8 -0
  49. data/test/dummy/config/initializers/sync_client.rb +14 -0
  50. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  51. data/test/dummy/config/locales/en.yml +5 -0
  52. data/test/dummy/config/routes.rb +58 -0
  53. data/test/dummy/lib/tasks/sync_client.rake +5 -0
  54. data/test/dummy/public/404.html +26 -0
  55. data/test/dummy/public/422.html +26 -0
  56. data/test/dummy/public/500.html +25 -0
  57. data/test/dummy/public/favicon.ico +0 -0
  58. data/test/dummy/script/rails +6 -0
  59. data/test/dummy/script/sync_client +8 -0
  60. data/test/lib/configurator_test.rb +14 -0
  61. data/test/lib/publisher_test.rb +43 -0
  62. data/test/lib/queue_publisher_test.rb +34 -0
  63. data/test/lib/sync_queue_test.rb +60 -0
  64. data/test/sync_client_test.rb +12 -0
  65. data/test/test_helper.rb +39 -0
  66. metadata +335 -0
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # SyncClient
2
+
3
+ This gem simplifies syncing data between services by using a resque queue and a message queue for guaranteed delivery and eventual consistency of data.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'sync_client'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Then setup your app as a client (if it isn't already):
20
+
21
+ ```bash
22
+ $ rails g sync_client:install
23
+ ```
24
+
25
+ Edit configuation in `config/initializers/sync_client.rb`
26
+
27
+ ## Usage
28
+
29
+ Within the model you want to publish attributes to a service include something like the following:
30
+
31
+ ```ruby
32
+ class Team
33
+ include SyncClient::Publisher
34
+ publish_changes_of :name, :color, to: :queue, for: [:update, :destroy], if: lambda{|team| !team.name.nil?}
35
+ # options:
36
+ # to: name of queue for publishing (required)
37
+ # for: callbacks for publishing (default: :create, :update, :destroy)
38
+ # if/unless: condition for publishing
39
+ end
40
+ ```
41
+
42
+
43
+ A priority is used for publishing to ensure eventual delivery if the message queue does not respond. Supported priority queues include Delayed Job and Resque.
44
+
45
+ Run the message queue poller:
46
+
47
+ ```bash
48
+ $ bundle exec script/sync_client start
49
+ $ bundle exec script/sync_client status
50
+ $ bundle exec script/sync_client restart
51
+ $ bundle exec script/sync_client stop
52
+ ```
53
+
54
+ Define handlers for the all messages such as the following:
55
+
56
+ ```ruby
57
+ class Game < SyncClient::ServiceResource::Base
58
+ attr_accessor :id
59
+ attr_accessor :starts_at
60
+ attr_accessor :ends_at
61
+
62
+ def create
63
+ Game.create(:game_id => self.id, :starts_at => self.starts_at, :ends_at => self.ends_at)
64
+ end
65
+
66
+ def update
67
+ game = Game.find(self.id).first
68
+ game.update_attributes(:starts_at => self.starts_at, :ends_at => self.ends_at)
69
+ end
70
+
71
+ def destroy
72
+ game = Game.find(self.id)
73
+ game.each do |ga|
74
+ ga.destroy
75
+ end
76
+ end
77
+ end
78
+ ```
79
+
80
+
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ Bundler::GemHelper.install_tasks
16
+
17
+ require 'rake/testtask'
18
+
19
+ Rake::TestTask.new(:test) do |t|
20
+ t.libs << 'lib'
21
+ t.libs << 'test'
22
+ t.pattern = 'test/**/*_test.rb'
23
+ t.verbose = false
24
+ end
25
+
26
+
27
+ task :default => :test
@@ -0,0 +1,12 @@
1
+ module Jobs
2
+ module SyncClientJobs
3
+ class Publish
4
+
5
+ @queue = :high
6
+
7
+ def self.perform(message)
8
+ SyncClient::PubMessage.new(message).synchronous_publish
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module SyncClient
2
+ class Message
3
+ attr_accessor :action
4
+ attr_accessor :object_type
5
+ attr_accessor :queue
6
+ attr_accessor :object_attributes
7
+
8
+ def initialize(attrs)
9
+ attrs = {} unless attrs
10
+ attrs.each do |key, value|
11
+ send("#{key}=", value) if self.respond_to?("#{key}=")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ module SyncClient
2
+ class PubMessage < Message
3
+
4
+ def publish
5
+ SyncClient.background_task_queue.enqueue(Jobs::SyncClientJobs::Publish, self)
6
+ end
7
+
8
+ def synchronous_publish
9
+ with_logging do
10
+ Queuel.with(queue_with_suffix).push package
11
+ end
12
+ end
13
+
14
+ def package
15
+ {:action => action, :object_type => object_type_with_service, :object_attributes => object_attributes}
16
+ end
17
+
18
+ def object_type_with_service
19
+ service = Rails.application.class.parent_name
20
+ "#{service}::#{object_type}"
21
+ end
22
+
23
+ def queue_with_suffix
24
+ SyncClient.queue_suffix.blank? ? queue.to_s : "#{queue}_#{SyncClient.queue_suffix}"
25
+ end
26
+
27
+ def with_logging(&block)
28
+ SyncClient.logger.info("------------------------------------------")
29
+ SyncClient.logger.info("Publishing Message: #{object_type}##{action}")
30
+ SyncClient.logger.info("To: #{queue_with_suffix}")
31
+ yield
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ module SyncClient
2
+ module ServiceResource
3
+ class Base
4
+ attr_accessor :error
5
+
6
+ # add logic to handle params
7
+ def initialize(attrs)
8
+ attrs = {} unless attrs
9
+ attrs.each do |key, value|
10
+ send("#{key}=", value) if self.respond_to?("#{key}=")
11
+ end
12
+ end
13
+
14
+ def create
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def update
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def destroy
23
+ raise NotImplementedError
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,38 @@
1
+ module SyncClient
2
+ class SubMessage < Message
3
+ attr_accessor :success
4
+
5
+ def process
6
+ with_logging do
7
+ self.success = message_handler.send(action.to_sym) if handler_present?
8
+ end
9
+ !!success
10
+ end
11
+
12
+ def handler_present?
13
+ return true if handler_class and handler_class.actions.include?(action.to_sym)
14
+ self.success = true #still setting to true to remove message from queue
15
+ return false
16
+ end
17
+
18
+ def handler_class
19
+ ::SyncClient.handlers[object_type]
20
+ end
21
+
22
+ def message_handler
23
+ @handler ||= handler_class.handler.new(object_attributes)
24
+ end
25
+
26
+ def error
27
+ message_handler.error if handler_class and message_handler.error
28
+ end
29
+
30
+ def with_logging(&block)
31
+ SyncClient.logger.info("------------------------------------------")
32
+ SyncClient.logger.info("Recieved Message: #{object_type}##{action}")
33
+ yield
34
+ SyncClient.logger.info("Error Occured: #{error}") if error
35
+ SyncClient.logger.info("Processed Message: #{!!success}")
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ require 'rails/generators'
2
+ module SyncClient
3
+ module Generators
4
+ class InstallGenerator < Rails::Generators::Base
5
+ source_root File.expand_path("../../templates", __FILE__)
6
+ desc "Creates a SyncClient initializer and copy other files to your application."
7
+ # class_option :bleh
8
+
9
+ def copy_initializer
10
+ template "sync_client.rb", "config/initializers/sync_client.rb"
11
+ end
12
+
13
+ def create_executable_file
14
+ if ActiveSupport::VERSION::MAJOR >= 4
15
+ prefix = 'bin'
16
+ else
17
+ prefix = 'script'
18
+ end
19
+ template "script", "#{prefix}/sync_client"
20
+ chmod "#{prefix}/sync_client", 0755
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ require 'daemons'
3
+ require 'sync_client'
4
+
5
+ Daemons.run_proc('sync_client', :dir_mode => :script, :dir => 'tmp/pids') do
6
+ require_relative File.join('..', 'config', 'environment.rb')
7
+ SyncClient::Poller.run
8
+ end
@@ -0,0 +1,13 @@
1
+ SyncClient.config do |config|
2
+
3
+ config.queuel do |c|
4
+ # c.default_queue 'VenueService'
5
+ # c.credentials token: 'asdufasdf8a7sd8fa7sdf', project_id: 'project_id'
6
+ # c.engine :iron_mq
7
+ end
8
+ # config.background_task_queue SyncClient::Resque #or SyncClient::DelayedJob or write your own.
9
+ # config.queue_suffix 'suffix'
10
+ # config.logger Logger.new
11
+ # config.add_message_object_handler object_name, handler_class, actions
12
+ # config.add_message_object_handler 'StatNgin::Game', 'Resource::StatNgin::Game', [:update, :create, :destroy]
13
+ end
@@ -0,0 +1,46 @@
1
+ require 'rails'
2
+ require 'queuel'
3
+
4
+ require 'sync_client/publisher'
5
+ require 'sync_client/queue_publisher'
6
+ require 'sync_client/sync_queue'
7
+ require 'sync_client/version'
8
+ require "sync_client/engine"
9
+ require 'sync_client/configurator'
10
+ require 'sync_client/worker'
11
+ require 'sync_client/poller'
12
+
13
+ module SyncClient
14
+
15
+ def self.version_string
16
+ "SyncClient version #{SyncClient::VERSION}"
17
+ end
18
+
19
+ def self.config
20
+ @config ||= Configurator.new
21
+ if block_given?
22
+ yield @config
23
+ end
24
+ @config
25
+ end
26
+
27
+ def self.handlers
28
+ config.handlers
29
+ end
30
+
31
+ def self.actions
32
+ Configurator::ACTIONS
33
+ end
34
+
35
+ def self.logger
36
+ config.sync_logger
37
+ end
38
+
39
+ def self.queue_suffix
40
+ config.suffix
41
+ end
42
+
43
+ def self.background_task_queue
44
+ config.task_queue
45
+ end
46
+ end
@@ -0,0 +1,15 @@
1
+ module SyncClient
2
+ module Configurations
3
+ class MessageHandler
4
+ attr_reader :name
5
+ attr_reader :handler
6
+ attr_reader :actions
7
+
8
+ def initialize(name, handler, actions = [])
9
+ @name = name.to_s.singularize
10
+ @handler = handler.classify.constantize
11
+ @actions = actions
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,53 @@
1
+ require 'sync_client/configurators/message_handlers'
2
+ require 'sync_client/task_queue/delayed_job'
3
+ require 'sync_client/task_queue/resque'
4
+ require 'sync_client/task_queue/inline_task_queue'
5
+
6
+ module SyncClient
7
+ class Configurator
8
+ private
9
+ attr_writer :message_handlers
10
+ attr_writer :suffix
11
+ attr_writer :sync_logger
12
+ attr_writer :task_queue
13
+
14
+ public
15
+ attr_reader :message_handlers
16
+ attr_reader :sync_logger
17
+ attr_reader :suffix
18
+ attr_reader :task_queue
19
+
20
+ ACTIONS = [:create, :update, :destroy]
21
+
22
+ def initialize
23
+ self.message_handlers = Configurators::MessageHandlers.new
24
+ self.sync_logger = Logger.new(STDOUT)
25
+ self.task_queue = SyncClient::InlineTaskQueue
26
+ end
27
+
28
+ def queuel
29
+ ::Queuel.configure { |c| yield c }
30
+ end
31
+
32
+ def add_message_handler(message, handler, actions)
33
+ message_handlers.add_message_handler message, handler, actions
34
+ end
35
+
36
+ def handlers
37
+ message_handlers.message_handlers
38
+ end
39
+
40
+ def queue_suffix(queue_suffix)
41
+ self.suffix = queue_suffix
42
+ end
43
+
44
+ def logger(logger)
45
+ self.sync_logger = logger
46
+ end
47
+
48
+ def background_task_queue(queue)
49
+ self.task_queue = queue
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,15 @@
1
+ require 'sync_client/configurations/message_handler'
2
+ module SyncClient
3
+ module Configurators
4
+ class MessageHandlers
5
+ attr_reader :message_handlers
6
+ def initialize
7
+ @message_handlers = {}.with_indifferent_access
8
+ end
9
+
10
+ def add_message_handler(name, handler, actions)
11
+ message_handlers[name] ||= Configurations::MessageHandler.new(name, handler, actions)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module SyncClient
2
+ class Engine < ::Rails::Engine
3
+
4
+ initializer "sync_client.eager_load", after: :set_autoload_paths do |app|
5
+ paths["app/models"].each do |load_path|
6
+ matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/
7
+ Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
8
+ require_dependency file.sub(matcher, '\1')
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end