synchronisable 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +3 -0
- data/.ruby-version +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +9 -0
- data/Guardfile +14 -0
- data/LICENSE.txt +22 -0
- data/README.md +22 -0
- data/Rakefile +7 -0
- data/TODO.md +20 -0
- data/bin/autospec +16 -0
- data/bin/rake +16 -0
- data/bin/rspec +16 -0
- data/lib/generators/synchronizable/USAGE +2 -0
- data/lib/generators/synchronizable/install_generator.rb +24 -0
- data/lib/generators/synchronizable/templates/create_imports_migration.rb +21 -0
- data/lib/generators/synchronizable/templates/initializer.rb +14 -0
- data/lib/synchronizable.rb +92 -0
- data/lib/synchronizable/context.rb +25 -0
- data/lib/synchronizable/dsl/associations.rb +57 -0
- data/lib/synchronizable/dsl/associations/association.rb +59 -0
- data/lib/synchronizable/dsl/associations/has_many.rb +12 -0
- data/lib/synchronizable/dsl/associations/has_one.rb +13 -0
- data/lib/synchronizable/dsl/macro.rb +100 -0
- data/lib/synchronizable/dsl/macro/attribute.rb +41 -0
- data/lib/synchronizable/dsl/macro/expression.rb +51 -0
- data/lib/synchronizable/dsl/macro/method.rb +15 -0
- data/lib/synchronizable/error_handler.rb +50 -0
- data/lib/synchronizable/exceptions.rb +8 -0
- data/lib/synchronizable/locale/en.yml +17 -0
- data/lib/synchronizable/locale/ru.yml +17 -0
- data/lib/synchronizable/model.rb +54 -0
- data/lib/synchronizable/model/methods.rb +55 -0
- data/lib/synchronizable/models/import.rb +24 -0
- data/lib/synchronizable/source.rb +72 -0
- data/lib/synchronizable/synchronizer.rb +177 -0
- data/lib/synchronizable/synchronizers/synchronizer_default.rb +10 -0
- data/lib/synchronizable/version.rb +10 -0
- data/lib/synchronizable/worker.rb +191 -0
- data/spec/dummy/.gitignore +16 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +16 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/gateways/gateway_base.rb +19 -0
- data/spec/dummy/app/gateways/match_gateway.rb +15 -0
- data/spec/dummy/app/gateways/player_gateway.rb +26 -0
- data/spec/dummy/app/gateways/stage_gateway.rb +18 -0
- data/spec/dummy/app/gateways/team_gateway.rb +18 -0
- data/spec/dummy/app/gateways/tournament_gateway.rb +15 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/match.rb +10 -0
- data/spec/dummy/app/models/match_player.rb +15 -0
- data/spec/dummy/app/models/player.rb +5 -0
- data/spec/dummy/app/models/stadium.rb +7 -0
- data/spec/dummy/app/models/stage.rb +6 -0
- data/spec/dummy/app/models/team.rb +5 -0
- data/spec/dummy/app/models/tournament.rb +7 -0
- data/spec/dummy/app/synchronizers/break_convention_team_synchronizer.rb +14 -0
- data/spec/dummy/app/synchronizers/match_synchronizer.rb +71 -0
- data/spec/dummy/app/synchronizers/player_synchronizer.rb +19 -0
- data/spec/dummy/app/synchronizers/stage_synchronizer.rb +20 -0
- data/spec/dummy/app/synchronizers/tournament_synchronizer.rb +20 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +26 -0
- data/spec/dummy/config/boot.rb +6 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +33 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +20 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/synchronizable.rb +2 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +56 -0
- data/spec/dummy/db/migrate/20140422132431_create_teams.rb +11 -0
- data/spec/dummy/db/migrate/20140422132544_create_matches.rb +12 -0
- data/spec/dummy/db/migrate/20140422132708_create_players.rb +15 -0
- data/spec/dummy/db/migrate/20140422133122_create_match_players.rb +12 -0
- data/spec/dummy/db/migrate/20140422135244_create_imports.rb +21 -0
- data/spec/dummy/db/migrate/20140422140817_create_stadiums.rb +10 -0
- data/spec/dummy/db/migrate/20140507135800_create_tournaments.rb +13 -0
- data/spec/dummy/db/migrate/20140507135837_create_stages.rb +13 -0
- data/spec/dummy/db/migrate/20140507140039_add_stage_id_to_matches.rb +6 -0
- data/spec/dummy/db/schema.rb +103 -0
- data/spec/dummy/db/seeds.rb +7 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/lib/tasks/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/robots.txt +5 -0
- data/spec/dummy/vendor/assets/javascripts/.keep +0 -0
- data/spec/dummy/vendor/assets/stylesheets/.keep +0 -0
- data/spec/factories/import.rb +5 -0
- data/spec/factories/match.rb +9 -0
- data/spec/factories/match_player.rb +9 -0
- data/spec/factories/player.rb +10 -0
- data/spec/factories/remote/match.rb +21 -0
- data/spec/factories/remote/player.rb +18 -0
- data/spec/factories/remote/stage.rb +26 -0
- data/spec/factories/remote/team.rb +21 -0
- data/spec/factories/remote/tournament.rb +18 -0
- data/spec/factories/stadium.rb +7 -0
- data/spec/factories/team.rb +16 -0
- data/spec/models/match_spec.rb +41 -0
- data/spec/models/team_spec.rb +85 -0
- data/spec/spec_helper.rb +64 -0
- data/spec/synchronizable/dsl/macro_spec.rb +59 -0
- data/spec/synchronizable/models/import_spec.rb +10 -0
- data/spec/synchronizable/support/has_macro.rb +12 -0
- data/spec/synchronizable/support/has_macro_subclass.rb +9 -0
- data/spec/synchronizable/synchronizable_spec.rb +60 -0
- data/synchronizable.gemspec +39 -0
- metadata +506 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'synchronizable/dsl/macro'
|
|
2
|
+
|
|
3
|
+
module Synchronizable
|
|
4
|
+
module DSL
|
|
5
|
+
module Associations
|
|
6
|
+
# Association builder.
|
|
7
|
+
class Association
|
|
8
|
+
include Synchronizable::DSL::Macro
|
|
9
|
+
|
|
10
|
+
attribute :key_suffix, default: -> { raise NotImplementedError }
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
attr_accessor :valid_options
|
|
14
|
+
|
|
15
|
+
def create(synchronizer, name, options)
|
|
16
|
+
new(synchronizer, name).create(options)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
self.valid_options = %i(key class_name required)
|
|
21
|
+
|
|
22
|
+
attr_reader :name, :model, :key, :required
|
|
23
|
+
|
|
24
|
+
def initialize(synchronizer, name)
|
|
25
|
+
@synchronizer, @name = synchronizer, name.to_sym
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create(options)
|
|
29
|
+
validate_options(options)
|
|
30
|
+
|
|
31
|
+
@key = options[:key]
|
|
32
|
+
@required = options[:required]
|
|
33
|
+
|
|
34
|
+
if options[:class_name].present?
|
|
35
|
+
@model = options[:class_name].constantize
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
set_defaults
|
|
39
|
+
|
|
40
|
+
@synchronizer.associations[@key] = self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
|
|
45
|
+
def set_defaults
|
|
46
|
+
@required ||= false
|
|
47
|
+
@model ||= @name.to_s.classify.constantize
|
|
48
|
+
@key = "#{@name}_#{self.class.key_suffix}" unless @key.present?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def validate_options(options)
|
|
54
|
+
options.assert_valid_keys(Association.valid_options)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require 'synchronizable/dsl/macro/method'
|
|
2
|
+
require 'synchronizable/dsl/macro/attribute'
|
|
3
|
+
|
|
4
|
+
module Synchronizable
|
|
5
|
+
module DSL
|
|
6
|
+
# Allows to define DSL-like attributes and methods.
|
|
7
|
+
# to be used in target class/module.
|
|
8
|
+
#
|
|
9
|
+
# @example Common use cases
|
|
10
|
+
# class Foo
|
|
11
|
+
# include Synchronizable::DSL::Macro
|
|
12
|
+
#
|
|
13
|
+
# attribute :bar, default: 1
|
|
14
|
+
# attribute :blah, default: -> { bar * 2 }
|
|
15
|
+
# attribute :x, :y, :z
|
|
16
|
+
# attribute :f, -> {
|
|
17
|
+
# ...
|
|
18
|
+
# }
|
|
19
|
+
# attribute :w, converter: ->(h) { x.with_indifferent_access }
|
|
20
|
+
# method :sqr, default: ->(x) { x * x }
|
|
21
|
+
# method :sqrt, :sin, :cos, default: -> { raise NotImplementedError }
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# class Bar < Foo
|
|
25
|
+
# bar 4
|
|
26
|
+
# blah "blah blah"
|
|
27
|
+
#
|
|
28
|
+
# sqr do |x|
|
|
29
|
+
# x * x
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# @api private
|
|
34
|
+
#
|
|
35
|
+
# @see Synchronizable::Synchronizer
|
|
36
|
+
# @see Synchronizable::DSL::Macro::Expression
|
|
37
|
+
# @see Synchronizable::DSL::Macro::Attribute
|
|
38
|
+
# @see Synchronizable::DSL::Macro::Method
|
|
39
|
+
module Macro
|
|
40
|
+
extend ActiveSupport::Concern
|
|
41
|
+
|
|
42
|
+
included do |base|
|
|
43
|
+
class_attribute :class_attributes
|
|
44
|
+
self.class_attributes = {
|
|
45
|
+
base => {}
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
module ClassMethods
|
|
50
|
+
def inherited(subclass)
|
|
51
|
+
class_attributes[subclass] = class_attributes[self].deep_dup
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Defines a new attribute.
|
|
55
|
+
#
|
|
56
|
+
# @overload attribute(*attrs, options)
|
|
57
|
+
# @param attrs [Array] attributes
|
|
58
|
+
# @param options [Hash]
|
|
59
|
+
# @option options :default default value,
|
|
60
|
+
# procs & lambdas are lazily evaluated
|
|
61
|
+
# @option options [Lambda] :converter method that
|
|
62
|
+
# will be lazily applied to the source value in order to convert it
|
|
63
|
+
# to the desired type
|
|
64
|
+
# @overload attribute(*attrs)
|
|
65
|
+
#
|
|
66
|
+
# @api private
|
|
67
|
+
def attribute(*args)
|
|
68
|
+
define_expressions(Attribute, args)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Defines a new method.
|
|
72
|
+
#
|
|
73
|
+
# @overload method(*attrs, options)
|
|
74
|
+
# @param attrs [Array] attributes
|
|
75
|
+
# @param options [Hash]
|
|
76
|
+
# @option options :default default value,
|
|
77
|
+
# procs & lambdas are lazily evaluated
|
|
78
|
+
# @overload method(*attrs)
|
|
79
|
+
#
|
|
80
|
+
# @api private
|
|
81
|
+
def method(*args)
|
|
82
|
+
define_expressions(Method, args)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def define_expressions(klass, args)
|
|
88
|
+
options = args.extract_options!
|
|
89
|
+
args.each do |attr|
|
|
90
|
+
name = attr.to_sym
|
|
91
|
+
class_attributes[self][name] = klass.new(options)
|
|
92
|
+
define_singleton_method(name) do |*params, &block|
|
|
93
|
+
class_attributes[self][name].handle(params, &block)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'synchronizable/dsl/macro/expression'
|
|
2
|
+
|
|
3
|
+
module Synchronizable
|
|
4
|
+
module DSL
|
|
5
|
+
module Macro
|
|
6
|
+
# Expression for an attribute definition.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
#
|
|
10
|
+
# @see Synchronizable::DSL::Macro
|
|
11
|
+
# @see Synchronizable::DSL::Expression
|
|
12
|
+
class Attribute < Expression
|
|
13
|
+
def initialize(options)
|
|
14
|
+
@converter = options[:converter]
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def source
|
|
19
|
+
transform(@source)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def default
|
|
23
|
+
transform(@default)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
def transform(arg)
|
|
29
|
+
convert(super)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def convert(arg)
|
|
35
|
+
@converter.try(:call, arg) || arg
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Synchronizable
|
|
2
|
+
module DSL
|
|
3
|
+
module Macro
|
|
4
|
+
# Serves as storage and provides lazy evaluation for expression.
|
|
5
|
+
# You can override attributes in subclasses,
|
|
6
|
+
# for example you can change the default value.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
#
|
|
10
|
+
# @see Synchronizable::DSL::Macro
|
|
11
|
+
# @see Synchronizable::DSL::Attribute
|
|
12
|
+
# @see Synchronizable::DSL::Method
|
|
13
|
+
class Expression
|
|
14
|
+
attr_reader :source, :default
|
|
15
|
+
|
|
16
|
+
def initialize(options)
|
|
17
|
+
@default = options[:default]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def handle(args, &block)
|
|
21
|
+
has_value = block || args.present?
|
|
22
|
+
has_value ? save(args, block) : value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
def transform(arg)
|
|
28
|
+
evaluate(arg)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def save(args, block)
|
|
34
|
+
if args.present?
|
|
35
|
+
@source = args.count > 1 ? args : args.first
|
|
36
|
+
elsif block
|
|
37
|
+
@source = block
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def value
|
|
42
|
+
source || default
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def evaluate(arg)
|
|
46
|
+
arg.try(:call) || arg
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'synchronizable/dsl/macro/expression'
|
|
2
|
+
|
|
3
|
+
module Synchronizable
|
|
4
|
+
module DSL
|
|
5
|
+
module Macro
|
|
6
|
+
# Expression for a method definition.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
#
|
|
10
|
+
# @see Synchronizable::DSL::Macro
|
|
11
|
+
# @see Synchronizable::DSL::Expression
|
|
12
|
+
class Method < Expression; end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Synchronizable
|
|
2
|
+
# Helper class for synchronization errors handling.
|
|
3
|
+
#
|
|
4
|
+
# @see Synchronizable::Context
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
class ErrorHandler
|
|
8
|
+
# @param logger [Logger] logger to used to log errors
|
|
9
|
+
# @param context [Synchronizable::Context] synchronization context
|
|
10
|
+
def initialize(logger, context)
|
|
11
|
+
@logger, @context = logger, context
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Wraps the given block in transaction.
|
|
15
|
+
# Rescued exceptions are written to log and saved to errors array.
|
|
16
|
+
#
|
|
17
|
+
# @param source [Synchronizable::Source] synchronization source
|
|
18
|
+
#
|
|
19
|
+
# @return [Boolean] `true` if syncronization was completed
|
|
20
|
+
# without errors, `false` otherwise
|
|
21
|
+
def handle(source)
|
|
22
|
+
ActiveRecord::Base.transaction do
|
|
23
|
+
yield
|
|
24
|
+
return true
|
|
25
|
+
end
|
|
26
|
+
rescue Exception => e
|
|
27
|
+
@context.errors << e
|
|
28
|
+
log(e, source)
|
|
29
|
+
return false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def log(e, source)
|
|
35
|
+
msg = error_message(e, source)
|
|
36
|
+
@logger.error msg
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def error_message(e, source)
|
|
40
|
+
I18n.t('errors.import_error',
|
|
41
|
+
:model => @context.model.to_s,
|
|
42
|
+
:error => e.message,
|
|
43
|
+
:remote_attrs => source.remote_attrs,
|
|
44
|
+
:local_attrs => source.local_attrs,
|
|
45
|
+
:import_record => source.import_record.inspect,
|
|
46
|
+
:local_record => source.local_record.inspect
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module Synchronizable
|
|
2
|
+
# Error is thrown when remote id isn't supplied
|
|
3
|
+
# with remote attibutes hash.
|
|
4
|
+
class MissedRemoteIdError < StandardError; end
|
|
5
|
+
# Error is thrown when required associations isn't supplied
|
|
6
|
+
# with remote attributes hash.
|
|
7
|
+
class MissedAssociationsError < StandardError; end
|
|
8
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
en:
|
|
3
|
+
errors:
|
|
4
|
+
missed_remote_id: Couldn't find remote id '%{remote_id}' in given data hash
|
|
5
|
+
missed_associations: Couldn't find associations %{keys} in %{attrs}
|
|
6
|
+
import_error: |
|
|
7
|
+
%{model} import error: %{error}.
|
|
8
|
+
remote attrs: %{remote_attrs},
|
|
9
|
+
local attrs: %{local_attrs},
|
|
10
|
+
import record: %{import_record},
|
|
11
|
+
local record: %{local_record}
|
|
12
|
+
gateway_method_missing: Method %{method} is not implemented for gateway
|
|
13
|
+
messages:
|
|
14
|
+
result: |
|
|
15
|
+
%{model} synchronization, parent: %{parent}.
|
|
16
|
+
Record count before / after: %{before} / %{after},
|
|
17
|
+
deleted record count: %{deleted}, error count: %{errors}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
ru:
|
|
3
|
+
errors:
|
|
4
|
+
missed_remote_id: В хэше не указан remote id '%{remote_id}'
|
|
5
|
+
missed_associations: В хэше %{attrs} не указаны ассоциации %{keys}
|
|
6
|
+
import_error: >
|
|
7
|
+
Ошибка при импорте %{model}: %{error},
|
|
8
|
+
remote attrs: %{remote_attrs},
|
|
9
|
+
local attrs: %{local_attrs},
|
|
10
|
+
import record: %{import_record},
|
|
11
|
+
local record: %{local_record}
|
|
12
|
+
gateway_method_missing: Не реализован метод '%{method}' для Gateway
|
|
13
|
+
messages:
|
|
14
|
+
result: >
|
|
15
|
+
Синхронизация %{model}, родительская запись: %{parent}.
|
|
16
|
+
Количество записей до: %{before}, количество записей после: %{after},
|
|
17
|
+
количество удаленных записей: %{deleted}, количество ошибок: %{errors}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'synchronizable/model/methods'
|
|
2
|
+
require 'synchronizable/synchronizers/synchronizer_default'
|
|
3
|
+
|
|
4
|
+
module Synchronizable
|
|
5
|
+
module Model
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
SYNCHRONIZER_SUFFIX = 'Synchronizer'
|
|
10
|
+
|
|
11
|
+
# Declare this on your model class to make it synchronizable.
|
|
12
|
+
# After that you can call {Synchronizable::Model::Methods#sync} to
|
|
13
|
+
# start model synchronization.
|
|
14
|
+
#
|
|
15
|
+
# @overload synchronizable(klass, options)
|
|
16
|
+
# @param klass [Class] synchronizer class to be used
|
|
17
|
+
# @param options [Hash] describes behavior of synchronizable model
|
|
18
|
+
# @option options [Class] :synchronizer class that provides
|
|
19
|
+
# synchronization configuration
|
|
20
|
+
# @overload synchronizable(options)
|
|
21
|
+
# @overload synchronizable
|
|
22
|
+
#
|
|
23
|
+
# @see Synchronizable::Synchronizer
|
|
24
|
+
# @see Synchronizable::Model::Methods
|
|
25
|
+
#
|
|
26
|
+
# @example Common usage
|
|
27
|
+
# class FooModel < ActiveRecord::Base
|
|
28
|
+
# synchronizable BarSynchronizer
|
|
29
|
+
# end
|
|
30
|
+
def synchronizable(*args)
|
|
31
|
+
extend Synchronizable::Model::Methods
|
|
32
|
+
|
|
33
|
+
class_attribute :synchronizer
|
|
34
|
+
has_one :import, as: :synchronizable
|
|
35
|
+
|
|
36
|
+
set_defaults(args)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def set_defaults(args)
|
|
42
|
+
options = args.extract_options!
|
|
43
|
+
|
|
44
|
+
self.synchronizer = args.first ||
|
|
45
|
+
options[:synchronizer] || default_synchronizer
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def default_synchronizer
|
|
49
|
+
const_name = "#{self.name.demodulize}#{SYNCHRONIZER_SUFFIX}"
|
|
50
|
+
const_name.safe_constantize || SynchronizerDefault
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|