sync_client 0.0.14

Sign up to get free protection for your applications and to get access to all the features.
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