synchronisable 0.0.9 → 1.0.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.
- checksums.yaml +4 -4
- data/TODO.md +5 -4
- data/lib/synchronisable/controller.rb +105 -0
- data/lib/synchronisable/dsl/associations/association.rb +7 -4
- data/lib/synchronisable/dsl/associations/belongs_to.rb +16 -0
- data/lib/synchronisable/dsl/associations/has_many.rb +4 -0
- data/lib/synchronisable/dsl/associations/has_one.rb +4 -0
- data/lib/synchronisable/dsl/associations.rb +4 -5
- data/lib/synchronisable/error_handler.rb +3 -0
- data/lib/synchronisable/helper/logging.rb +23 -0
- data/lib/synchronisable/input_descriptor.rb +5 -0
- data/lib/synchronisable/{input_dispatcher.rb → input_parser.rb} +10 -8
- data/lib/synchronisable/locale/en.yml +1 -0
- data/lib/synchronisable/locale/ru.yml +1 -0
- data/lib/synchronisable/model/methods.rb +6 -6
- data/lib/synchronisable/source.rb +38 -34
- data/lib/synchronisable/synchronizer.rb +1 -1
- data/lib/synchronisable/version.rb +2 -2
- data/lib/synchronisable/worker/associations.rb +56 -0
- data/lib/synchronisable/worker/base.rb +23 -0
- data/lib/synchronisable/worker/record.rb +44 -0
- data/spec/dummy/app/gateways/team_group_statistic_gateway.rb +22 -0
- data/spec/dummy/app/models/stadium.rb +1 -1
- data/spec/dummy/app/models/team.rb +1 -0
- data/spec/dummy/app/models/team_group_statistic.rb +6 -0
- data/spec/dummy/app/synchronizers/team_group_statistic_synchronizer.rb +5 -0
- data/spec/dummy/db/migrate/20140609133855_create_team_group_statistic.rb +13 -0
- data/spec/dummy/db/schema.rb +13 -1
- data/spec/factories/remote/team_group_statistic.rb +13 -0
- data/spec/models/team_group_statistic_spec.rb +15 -0
- metadata +21 -4
- data/lib/synchronisable/worker.rb +0 -171
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 9ce97779ff8534be6e3b14f5e872e8788638badd
         | 
| 4 | 
            +
              data.tar.gz: 9d5796423c0e433d71103d972a1a070d2d690bb0
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 3f2d9314d4e4e4fa31c4061038051531279311b0e25bd33e15293dd29304c86f86eee1f9d44223f73029ffbc864df789f0759e2ab150f0dc9361350ae898528f
         | 
| 7 | 
            +
              data.tar.gz: f43e651e7528bd8e6ce9b7a09dba7ed6cb156e4f5ba4e66d5b1a8c906d877b78af7063c6fe8af44715392c27eaf63ca8199892cb44f078ba02fd24201f85cbde
         | 
    
        data/TODO.md
    CHANGED
    
    | @@ -5,14 +5,15 @@ Primary objectives | |
| 5 5 | 
             
            - [x] sync method
         | 
| 6 6 | 
             
            - [x] dependent syncronization & mapping
         | 
| 7 7 | 
             
            - [x] tests for Synchronisable.sync
         | 
| 8 | 
            -
            - [ ] fix a mess with Context
         | 
| 8 | 
            +
            - [ ] fix a mess with Context: return an array of sync contexts,
         | 
| 9 | 
            +
                  or aggregate all syncronization contexts into just one
         | 
| 9 10 | 
             
            - [ ] destroy_missed
         | 
| 10 | 
            -
            - [ | 
| 11 | 
            +
            - [x] worker.rb refactoring
         | 
| 11 12 | 
             
            - [x] integrate with travis, stillmaintained, gemnasium,
         | 
| 12 | 
            -
             | 
| 13 | 
            +
                  codeclimate, coveralls, inch-pages, codersclan
         | 
| 13 14 | 
             
            - [ ] write a good README
         | 
| 14 15 | 
             
            - [ ] extended interface
         | 
| 15 | 
            -
            - | 
| 16 | 
            +
              - [ ] sync with include
         | 
| 16 17 | 
             
              - [x] sync with ids array
         | 
| 17 18 | 
             
              - [ ] handle case when association type is a :hash
         | 
| 18 19 | 
             
              - [ ] sync method for collection proxy (Model.where(condition).sync)
         | 
| @@ -0,0 +1,105 @@ | |
| 1 | 
            +
            require 'pry-byebug'
         | 
| 2 | 
            +
            require 'colorize'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'synchronisable/error_handler'
         | 
| 5 | 
            +
            require 'synchronisable/context'
         | 
| 6 | 
            +
            require 'synchronisable/input_parser'
         | 
| 7 | 
            +
            require 'synchronisable/source'
         | 
| 8 | 
            +
            require 'synchronisable/models/import'
         | 
| 9 | 
            +
            require 'synchronisable/helper/logging'
         | 
| 10 | 
            +
            require 'synchronisable/worker/record'
         | 
| 11 | 
            +
            require 'synchronisable/worker/associations'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            module Synchronisable
         | 
| 14 | 
            +
              # Responsible for model synchronization.
         | 
| 15 | 
            +
              #
         | 
| 16 | 
            +
              # @api private
         | 
| 17 | 
            +
              class Controller
         | 
| 18 | 
            +
                include Helper::Logging
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                attr_reader :logger
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                class << self
         | 
| 23 | 
            +
                  # Creates a new instance of controller and initiates model synchronization.
         | 
| 24 | 
            +
                  #
         | 
| 25 | 
            +
                  # @overload call(model, data, options)
         | 
| 26 | 
            +
                  #   @param model [Class] model class to be synchronized
         | 
| 27 | 
            +
                  #   @param options [Hash] synchronization options
         | 
| 28 | 
            +
                  #   @option options [Hash] :include assocations to be synchronized.
         | 
| 29 | 
            +
                  #     Use this option to override `has_one` & `has_many` assocations
         | 
| 30 | 
            +
                  #     defined in model synchronizer.
         | 
| 31 | 
            +
                  # @overload call(model, data)
         | 
| 32 | 
            +
                  # @overload call(model)
         | 
| 33 | 
            +
                  #
         | 
| 34 | 
            +
                  # @return [Synchronisable::Context] synchronization context
         | 
| 35 | 
            +
                  def call(model, *args)
         | 
| 36 | 
            +
                    options = args.extract_options!
         | 
| 37 | 
            +
                    data = args.first
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    new(model, options).call(data)
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                # Initiates model synchronization.
         | 
| 44 | 
            +
                #
         | 
| 45 | 
            +
                # @param data [Array<Hash>, Array<String>, Array<Integer>, String, Integer]
         | 
| 46 | 
            +
                #   synchronization data.
         | 
| 47 | 
            +
                #   If not specified, it will try to get array of hashes to sync with
         | 
| 48 | 
            +
                #   using defined gateway class or `fetch` lambda/proc
         | 
| 49 | 
            +
                #   defined in corresponding synchronizer
         | 
| 50 | 
            +
                #
         | 
| 51 | 
            +
                # @return [Synchronisable::Context] synchronization context
         | 
| 52 | 
            +
                #
         | 
| 53 | 
            +
                # @see Synchronisable::InputParser
         | 
| 54 | 
            +
                def call(data)
         | 
| 55 | 
            +
                  sync do |context|
         | 
| 56 | 
            +
                    error_handler = ErrorHandler.new(logger, context)
         | 
| 57 | 
            +
                    context.before = @model.imports_count
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    hashes = @input.parse(data)
         | 
| 60 | 
            +
                    hashes.each do |attrs|
         | 
| 61 | 
            +
                      source = Source.new(@model, @parent, attrs)
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                      error_handler.handle(source) do
         | 
| 64 | 
            +
                        source.prepare
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                        record_worker = Worker::Record.new(@synchronizer, source)
         | 
| 67 | 
            +
                        associations_worker = Worker::Associations.new(@synchronizer, source)
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                        @synchronizer.with_sync_callbacks(source) do
         | 
| 70 | 
            +
                          associations_worker.sync_parent_associations
         | 
| 71 | 
            +
                          record_worker.sync_record
         | 
| 72 | 
            +
                          associations_worker.sync_child_associations
         | 
| 73 | 
            +
                        end
         | 
| 74 | 
            +
                      end
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    context.after = @model.imports_count
         | 
| 78 | 
            +
                    context.deleted = 0
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                private
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def initialize(model, options)
         | 
| 85 | 
            +
                  @model, @synchronizer = model, model.synchronizer
         | 
| 86 | 
            +
                  @logger = @synchronizer.logger
         | 
| 87 | 
            +
                  @parent = options[:parent]
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  @input = InputParser.new(@model, @synchronizer)
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                def sync
         | 
| 93 | 
            +
                  @logger.progname = "#{@model} synchronization"
         | 
| 94 | 
            +
                  log_info('STARTING', :yellow, true)
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  context = Context.new(@model, @parent.try(:model))
         | 
| 97 | 
            +
                  yield context
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  log_info('DONE', :yellow, true)
         | 
| 100 | 
            +
                  log_info(context.summary_message, :cyan, true)
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  context
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
              end
         | 
| 105 | 
            +
            end
         | 
| @@ -4,6 +4,7 @@ module Synchronisable | |
| 4 4 | 
             
              module DSL
         | 
| 5 5 | 
             
                module Associations
         | 
| 6 6 | 
             
                  # Association builder.
         | 
| 7 | 
            +
                  # Subclasses must implement #macro method.
         | 
| 7 8 | 
             
                  class Association
         | 
| 8 9 | 
             
                    include Synchronisable::DSL::Macro
         | 
| 9 10 |  | 
| @@ -17,9 +18,9 @@ module Synchronisable | |
| 17 18 | 
             
                      end
         | 
| 18 19 | 
             
                    end
         | 
| 19 20 |  | 
| 20 | 
            -
                    self.valid_options = %i(key class_name required | 
| 21 | 
            +
                    self.valid_options = %i(key class_name required)
         | 
| 21 22 |  | 
| 22 | 
            -
                    attr_reader :name, :model, :key, :required | 
| 23 | 
            +
                    attr_reader :name, :model, :key, :required
         | 
| 23 24 |  | 
| 24 25 | 
             
                    def initialize(synchronizer, name)
         | 
| 25 26 | 
             
                      @synchronizer, @name = synchronizer, name.to_sym
         | 
| @@ -30,7 +31,6 @@ module Synchronisable | |
| 30 31 |  | 
| 31 32 | 
             
                      @key = options[:key]
         | 
| 32 33 | 
             
                      @required = options[:required]
         | 
| 33 | 
            -
                      @type = options[:type]
         | 
| 34 34 |  | 
| 35 35 | 
             
                      if options[:class_name].present?
         | 
| 36 36 | 
             
                        @model = options[:class_name].constantize
         | 
| @@ -41,11 +41,14 @@ module Synchronisable | |
| 41 41 | 
             
                      @synchronizer.associations[@key] = self
         | 
| 42 42 | 
             
                    end
         | 
| 43 43 |  | 
| 44 | 
            +
                    def macro
         | 
| 45 | 
            +
                      raise NotImplementedError
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 44 48 | 
             
                    protected
         | 
| 45 49 |  | 
| 46 50 | 
             
                    def set_defaults
         | 
| 47 51 | 
             
                      @required ||= false
         | 
| 48 | 
            -
                      @type ||= :ids
         | 
| 49 52 |  | 
| 50 53 | 
             
                      @model ||= @name.to_s.classify.constantize
         | 
| 51 54 | 
             
                      @key = "#{@name}_#{self.class.key_suffix}" unless @key.present?
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            require 'synchronisable/dsl/associations/association'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Synchronisable
         | 
| 4 | 
            +
              module DSL
         | 
| 5 | 
            +
                module Associations
         | 
| 6 | 
            +
                  # `belongs_to` association builder.
         | 
| 7 | 
            +
                  class BelongsTo < Association
         | 
| 8 | 
            +
                    key_suffix 'id'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    def macro
         | 
| 11 | 
            +
                      :belongs_to
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| @@ -1,5 +1,6 @@ | |
| 1 1 | 
             
            require 'synchronisable/dsl/associations/has_one'
         | 
| 2 2 | 
             
            require 'synchronisable/dsl/associations/has_many'
         | 
| 3 | 
            +
            require 'synchronisable/dsl/associations/belongs_to'
         | 
| 3 4 |  | 
| 4 5 | 
             
            module Synchronisable
         | 
| 5 6 | 
             
              module DSL
         | 
| @@ -17,7 +18,7 @@ module Synchronisable | |
| 17 18 | 
             
                      subclass.associations = {}
         | 
| 18 19 | 
             
                    end
         | 
| 19 20 |  | 
| 20 | 
            -
                    [HasOne, HasMany].each do |klass|
         | 
| 21 | 
            +
                    [HasOne, HasMany, BelongsTo].each do |klass|
         | 
| 21 22 | 
             
                      macro = klass.to_s.demodulize.underscore.to_sym
         | 
| 22 23 | 
             
                      define_method(macro) do |name, options = {}|
         | 
| 23 24 | 
             
                        klass.create(self, name, options)
         | 
| @@ -34,11 +35,9 @@ module Synchronisable | |
| 34 35 | 
             
                    #   attributes hash doesn't required associations
         | 
| 35 36 | 
             
                    def associations_for(attrs)
         | 
| 36 37 | 
             
                      ensure_required_associations(attrs)
         | 
| 37 | 
            -
                      intersection = self.associations.map { |key, _| key } & attrs.keys
         | 
| 38 38 |  | 
| 39 | 
            -
                       | 
| 40 | 
            -
             | 
| 41 | 
            -
                      }]
         | 
| 39 | 
            +
                      intersection = self.associations.map { |key, _| key } & attrs.keys
         | 
| 40 | 
            +
                      Hash[intersection.map { |key| [self.associations[key], [*attrs[key]]] }]
         | 
| 42 41 | 
             
                    end
         | 
| 43 42 |  | 
| 44 43 | 
             
                    private
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            require 'pry-byebug'
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Synchronisable
         | 
| 2 4 | 
             
              # Helper class for synchronization errors handling.
         | 
| 3 5 | 
             
              #
         | 
| @@ -40,6 +42,7 @@ module Synchronisable | |
| 40 42 | 
             
                  I18n.t('errors.import_error',
         | 
| 41 43 | 
             
                    :model         => @context.model.to_s,
         | 
| 42 44 | 
             
                    :error         => e.message,
         | 
| 45 | 
            +
                    :backtrace     => e.backtrace.join("\n"),
         | 
| 43 46 | 
             
                    :remote_attrs  => source.remote_attrs,
         | 
| 44 47 | 
             
                    :local_attrs   => source.local_attrs,
         | 
| 45 48 | 
             
                    :import_record => source.import_record.inspect,
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            module Synchronisable
         | 
| 2 | 
            +
              module Helper
         | 
| 3 | 
            +
                # Provides logging helper methods.
         | 
| 4 | 
            +
                # Class or module that includes this one
         | 
| 5 | 
            +
                # should implement `logger` method.
         | 
| 6 | 
            +
                #
         | 
| 7 | 
            +
                # @api private
         | 
| 8 | 
            +
                module Logging
         | 
| 9 | 
            +
                  protected
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  %i(verbose colorize).each do |name|
         | 
| 12 | 
            +
                    define_method("#{name}_logging?".to_sym) do
         | 
| 13 | 
            +
                      Synchronisable.logging[name]
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def log_info(msg, color = :white, force = true)
         | 
| 18 | 
            +
                    text = msg.colorize(color) if colorize_logging?
         | 
| 19 | 
            +
                    logger.info(text) if force || verbose_logging?
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -1,20 +1,22 @@ | |
| 1 1 | 
             
            require 'synchronisable/input_descriptor'
         | 
| 2 2 |  | 
| 3 3 | 
             
            module Synchronisable
         | 
| 4 | 
            +
              # Responsible for guessing the user input format.
         | 
| 5 | 
            +
              #
         | 
| 4 6 | 
             
              # @api private
         | 
| 5 | 
            -
              class  | 
| 6 | 
            -
                class << self
         | 
| 7 | 
            -
                  def dispatch(model, synchronizer, data)
         | 
| 8 | 
            -
                    new(model, synchronizer).dispatch(data)
         | 
| 9 | 
            -
                  end
         | 
| 10 | 
            -
                end
         | 
| 11 | 
            -
             | 
| 7 | 
            +
              class InputParser
         | 
| 12 8 | 
             
                def initialize(model, synchronizer)
         | 
| 13 9 | 
             
                  @model = model
         | 
| 14 10 | 
             
                  @synchronizer = synchronizer
         | 
| 15 11 | 
             
                end
         | 
| 16 12 |  | 
| 17 | 
            -
                 | 
| 13 | 
            +
                # Parses the user input.
         | 
| 14 | 
            +
                #
         | 
| 15 | 
            +
                # @param data [Array<Hash>, Array<String>, Array<Integer>, String, Integer]
         | 
| 16 | 
            +
                #   synchronization data to handle.
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @return [Array<Hash>] array of hashes with remote attributes
         | 
| 19 | 
            +
                def parse(data)
         | 
| 18 20 | 
             
                  input = InputDescriptor.new(data)
         | 
| 19 21 |  | 
| 20 22 | 
             
                  result = case
         | 
| @@ -1,16 +1,17 @@ | |
| 1 | 
            -
            require 'synchronisable/ | 
| 1 | 
            +
            require 'synchronisable/controller'
         | 
| 2 2 | 
             
            require 'synchronisable/models/import'
         | 
| 3 3 |  | 
| 4 4 | 
             
            module Synchronisable
         | 
| 5 5 | 
             
              module Model
         | 
| 6 6 | 
             
                # Methods that will be attached to synchronisable model class.
         | 
| 7 7 | 
             
                module Methods
         | 
| 8 | 
            -
                  # Creates a new  | 
| 9 | 
            -
                  # for this particular model.
         | 
| 8 | 
            +
                  # Creates a new controller, that initiates synchronization
         | 
| 9 | 
            +
                  # for this particular model and its associations.
         | 
| 10 10 | 
             
                  # If you have implemented `fetch` & `find` methods
         | 
| 11 11 | 
             
                  # in your model synchronizer, than it will be used if no data supplied.
         | 
| 12 12 | 
             
                  #
         | 
| 13 13 | 
             
                  # @overload sync(data, options)
         | 
| 14 | 
            +
                  #   @param data [Array<Hash>, Array<String>, Array<Integer>, String, Integer] synchronization data
         | 
| 14 15 | 
             
                  #   @param options [Hash] synchronization options
         | 
| 15 16 | 
             
                  #   @option options [Hash] :include assocations to be synchronized.
         | 
| 16 17 | 
             
                  #     Use this option to override `has_one` & `has_many` assocations
         | 
| @@ -19,9 +20,8 @@ module Synchronisable | |
| 19 20 | 
             
                  # @overload sync(data)
         | 
| 20 21 | 
             
                  # @overload sync
         | 
| 21 22 | 
             
                  #
         | 
| 22 | 
            -
                  # @param data [Array<Hash>] array of hashes with remote attributes.
         | 
| 23 23 | 
             
                  #
         | 
| 24 | 
            -
                  # @see Synchronisable:: | 
| 24 | 
            +
                  # @see Synchronisable::Controller
         | 
| 25 25 | 
             
                  #
         | 
| 26 26 | 
             
                  # @example Supplying array of hashes with remote attributes
         | 
| 27 27 | 
             
                  #   FooModel.sync([
         | 
| @@ -43,7 +43,7 @@ module Synchronisable | |
| 43 43 | 
             
                  #     :match_players => :player
         | 
| 44 44 | 
             
                  #   })
         | 
| 45 45 | 
             
                  def sync(*args)
         | 
| 46 | 
            -
                     | 
| 46 | 
            +
                    Controller.call(self, *args)
         | 
| 47 47 | 
             
                  end
         | 
| 48 48 |  | 
| 49 49 | 
             
                  # Count of import records for this model.
         | 
| @@ -1,10 +1,15 @@ | |
| 1 1 | 
             
            module Synchronisable
         | 
| 2 | 
            +
              # TODO: Massive refactoring needed
         | 
| 3 | 
            +
             | 
| 2 4 | 
             
              # Synchronization source.
         | 
| 3 5 | 
             
              class Source
         | 
| 6 | 
            +
                CHILD_ASSOCIATION_KEYS = %i(has_one has_many)
         | 
| 7 | 
            +
                PARENT_ASSOCIATION_KEYS = %i(belongs_to)
         | 
| 8 | 
            +
             | 
| 4 9 | 
             
                attr_accessor :import_record
         | 
| 5 | 
            -
                attr_reader : | 
| 6 | 
            -
             | 
| 7 | 
            -
                            : | 
| 10 | 
            +
                attr_reader :parent_associations, :child_associations
         | 
| 11 | 
            +
                attr_reader :model, :remote_attrs, :remote_id,
         | 
| 12 | 
            +
                            :local_attrs, :import_ids
         | 
| 8 13 |  | 
| 9 14 | 
             
                def initialize(model, parent, remote_attrs)
         | 
| 10 15 | 
             
                  @model, @parent, @synchronizer = model, parent, model.synchronizer
         | 
| @@ -13,6 +18,7 @@ module Synchronisable | |
| 13 18 |  | 
| 14 19 | 
             
                # Prepares synchronization source:
         | 
| 15 20 | 
             
                # `remote_id`, `local_attributes`, `import_record` and `associations`.
         | 
| 21 | 
            +
                #
         | 
| 16 22 | 
             
                # Sets foreign key if current model is specified as `has_one` or `has_many`
         | 
| 17 23 | 
             
                # association of parent model.
         | 
| 18 24 | 
             
                #
         | 
| @@ -20,14 +26,14 @@ module Synchronisable | |
| 20 26 | 
             
                def prepare
         | 
| 21 27 | 
             
                  @remote_id = @synchronizer.extract_remote_id(@remote_attrs)
         | 
| 22 28 | 
             
                  @local_attrs = @synchronizer.map_attributes(@remote_attrs)
         | 
| 23 | 
            -
                  @associations = @synchronizer.associations_for(@local_attrs)
         | 
| 24 29 |  | 
| 25 | 
            -
                   | 
| 26 | 
            -
                   | 
| 30 | 
            +
                  @associations = @synchronizer.associations_for(@local_attrs)
         | 
| 31 | 
            +
                  @parent_associations = @associations.select do |association|
         | 
| 32 | 
            +
                    PARENT_ASSOCIATION_KEYS.include? association.macro
         | 
| 33 | 
            +
                  end
         | 
| 27 34 |  | 
| 28 | 
            -
                   | 
| 29 | 
            -
             | 
| 30 | 
            -
                    @associations.keys.any? { |a| a.key == key }
         | 
| 35 | 
            +
                  @child_associations = @associations.select do |association|
         | 
| 36 | 
            +
                    CHILD_ASSOCIATION_KEYS.include? association.macro
         | 
| 31 37 | 
             
                  end
         | 
| 32 38 |  | 
| 33 39 | 
             
                  @import_record = Import.find_by(
         | 
| @@ -35,25 +41,15 @@ module Synchronisable | |
| 35 41 | 
             
                    :synchronisable_type => @model
         | 
| 36 42 | 
             
                  )
         | 
| 37 43 |  | 
| 38 | 
            -
                   | 
| 39 | 
            -
                end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                def updatable?
         | 
| 42 | 
            -
                  @import_record.present? && local_record.present?
         | 
| 43 | 
            -
                end
         | 
| 44 | 
            +
                  remove_association_keys_from_local_attrs
         | 
| 44 45 |  | 
| 45 | 
            -
             | 
| 46 | 
            -
                   | 
| 46 | 
            +
                  # TODO: This should be in Synchronisable::RecordWorker
         | 
| 47 | 
            +
                  set_belongs_to_parent_foreign_key
         | 
| 47 48 | 
             
                end
         | 
| 48 49 |  | 
| 49 | 
            -
                def  | 
| 50 | 
            -
                   | 
| 51 | 
            -
                   | 
| 52 | 
            -
                    :synchronisable_id    => record.id,
         | 
| 53 | 
            -
                    :synchronisable_type  => @model.to_s,
         | 
| 54 | 
            -
                    :remote_id            => @remote_id.to_s,
         | 
| 55 | 
            -
                    :attrs                => @local_attrs
         | 
| 56 | 
            -
                  )
         | 
| 50 | 
            +
                def updatable?
         | 
| 51 | 
            +
                  import_record.present? &&
         | 
| 52 | 
            +
                  local_record.present?
         | 
| 57 53 | 
             
                end
         | 
| 58 54 |  | 
| 59 55 | 
             
                def local_record
         | 
| @@ -70,22 +66,30 @@ module Synchronisable | |
| 70 66 |  | 
| 71 67 | 
             
                private
         | 
| 72 68 |  | 
| 73 | 
            -
                def  | 
| 74 | 
            -
                   | 
| 75 | 
            -
             | 
| 76 | 
            -
                   | 
| 69 | 
            +
                def remove_association_keys_from_local_attrs
         | 
| 70 | 
            +
                  @local_attrs.delete_if do |key, _|
         | 
| 71 | 
            +
                    @associations.keys.any? { |a| a.key == key }
         | 
| 72 | 
            +
                  end
         | 
| 77 73 | 
             
                end
         | 
| 78 74 |  | 
| 79 | 
            -
                def  | 
| 80 | 
            -
                  return  | 
| 81 | 
            -
                   | 
| 75 | 
            +
                def set_belongs_to_parent_foreign_key
         | 
| 76 | 
            +
                  return unless @parent && parent_has_current_model_as_reflection?
         | 
| 77 | 
            +
                  @local_attrs[parent_foreign_key_name] = @parent.local_record.id
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def parent_foreign_key_name
         | 
| 82 82 | 
             
                  "#{parent_name}_id"
         | 
| 83 83 | 
             
                end
         | 
| 84 84 |  | 
| 85 | 
            -
                def  | 
| 85 | 
            +
                def parent_name
         | 
| 86 | 
            +
                  @parent.model.table_name.singularize
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def parent_has_current_model_as_reflection?
         | 
| 86 90 | 
             
                  @parent.model.reflections.values.any? do |reflection|
         | 
| 87 91 | 
             
                    reflection.plural_name == @model.table_name &&
         | 
| 88 | 
            -
                     | 
| 92 | 
            +
                    %i(has_one has_many).include?(reflection.macro)
         | 
| 89 93 | 
             
                  end
         | 
| 90 94 | 
             
                end
         | 
| 91 95 | 
             
              end
         | 
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            require 'synchronisable/worker/base'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Synchronisable
         | 
| 4 | 
            +
              module Worker
         | 
| 5 | 
            +
                # Responsible for associations synchronization.
         | 
| 6 | 
            +
                #
         | 
| 7 | 
            +
                # @api private
         | 
| 8 | 
            +
                class Associations < Base
         | 
| 9 | 
            +
                  # Synchronizes associations.
         | 
| 10 | 
            +
                  #
         | 
| 11 | 
            +
                  # @see Synchronisable::Source
         | 
| 12 | 
            +
                  # @see Synchronisable::DSL::Associations
         | 
| 13 | 
            +
                  # @see Synchronisable::DSL::Associations::Association
         | 
| 14 | 
            +
                  %w(child parent).each do |type|
         | 
| 15 | 
            +
                    define_method(:"sync_#{type}_associations") do
         | 
| 16 | 
            +
                      associations = @source.send(:"#{type}_associations")
         | 
| 17 | 
            +
                      log_info("starting #{type} associations sync", :blue) if associations.present?
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                      associations.each do |association, ids|
         | 
| 20 | 
            +
                        ids.each do |id|
         | 
| 21 | 
            +
                          send(:"sync_#{type}_association", id, association)
         | 
| 22 | 
            +
                        end
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      log_info("done #{type} associations sync", :blue) if associations.present?
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  private
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def sync_parent_association(id, association)
         | 
| 32 | 
            +
                    log_info("synchronizing parent association with id: #{id}", :blue)
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    @synchronizer.with_association_sync_callbacks(@source, id, association) do
         | 
| 35 | 
            +
                      attrs = association.model.synchronizer.find(id)
         | 
| 36 | 
            +
                      Controller.call(association.model, [attrs])
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                      import_record = Import.find_by(
         | 
| 39 | 
            +
                        :remote_id => id,
         | 
| 40 | 
            +
                        :synchronisable_type => association.model.to_s
         | 
| 41 | 
            +
                      )
         | 
| 42 | 
            +
                      @source.local_attrs[association.key] = import_record.synchronisable.id
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  def sync_child_association(id, association)
         | 
| 47 | 
            +
                    log_info("synchronizing child association with id: #{id}", :blue)
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    @synchronizer.with_association_sync_callbacks(@source, id, association) do
         | 
| 50 | 
            +
                      attrs = association.model.synchronizer.find(id)
         | 
| 51 | 
            +
                      Controller.call(association.model, [attrs], { :parent => @source })
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            require 'synchronisable/helper/logging'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Synchronisable
         | 
| 4 | 
            +
              module Worker
         | 
| 5 | 
            +
                # Base class for synchronization workers.
         | 
| 6 | 
            +
                #
         | 
| 7 | 
            +
                # @see Synchronisable::Worker::Record
         | 
| 8 | 
            +
                # @see Synchronisable::Worker::Associations
         | 
| 9 | 
            +
                #
         | 
| 10 | 
            +
                # @api private
         | 
| 11 | 
            +
                class Base
         | 
| 12 | 
            +
                  include Helper::Logging
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def initialize(synchronizer, source)
         | 
| 15 | 
            +
                    @synchronizer, @source = synchronizer, source
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def logger
         | 
| 19 | 
            +
                    @synchronizer.logger
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            require 'synchronisable/worker/base'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Synchronisable
         | 
| 4 | 
            +
              module Worker
         | 
| 5 | 
            +
                # Responsible for record synchronization.
         | 
| 6 | 
            +
                #
         | 
| 7 | 
            +
                # @api private
         | 
| 8 | 
            +
                class Record < Base
         | 
| 9 | 
            +
                  # Synchronizes record.
         | 
| 10 | 
            +
                  def sync_record
         | 
| 11 | 
            +
                    @synchronizer.with_record_sync_callbacks(@source) do
         | 
| 12 | 
            +
                      log_info(@source.dump_message, :green)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                      if @source.updatable?
         | 
| 15 | 
            +
                        log_info("updating #{@source.model}: #{@source.local_record.id}", :blue)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                        update_record
         | 
| 18 | 
            +
                      else
         | 
| 19 | 
            +
                        create_record_pair
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                        log_info("#{@source.model} (id: #{@source.local_record.id}) was created", :blue)
         | 
| 22 | 
            +
                        log_info("#{@source.import_record.class}: #{@source.import_record.id} was created", :blue)
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def update_record
         | 
| 30 | 
            +
                    @source.local_record.update_attributes!(@source.local_attrs)
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def create_record_pair
         | 
| 34 | 
            +
                    record = @source.model.create!(@source.local_attrs)
         | 
| 35 | 
            +
                    @source.import_record = Import.create!(
         | 
| 36 | 
            +
                      :synchronisable_id    => record.id,
         | 
| 37 | 
            +
                      :synchronisable_type  => @source.model.to_s,
         | 
| 38 | 
            +
                      :remote_id            => @source.remote_id.to_s,
         | 
| 39 | 
            +
                      :attrs                => @source.local_attrs
         | 
| 40 | 
            +
                    )
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            class TeamGroupStatisticGateway < GatewayBase
         | 
| 2 | 
            +
              def id_key
         | 
| 3 | 
            +
                :id
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def find(id)
         | 
| 7 | 
            +
                raise NotImplementedError, 'Life is a bitch'
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def source
         | 
| 11 | 
            +
                @source ||= [
         | 
| 12 | 
            +
                  FactoryGirl.build(:remote_team_group_statistic,
         | 
| 13 | 
            +
                    :id       => 'team_0',
         | 
| 14 | 
            +
                    :team_id  => 'team_0'
         | 
| 15 | 
            +
                  ),
         | 
| 16 | 
            +
                  FactoryGirl.build(:remote_team_group_statistic,
         | 
| 17 | 
            +
                    :id       => 'team_1',
         | 
| 18 | 
            +
                    :team_id  => 'team_1'
         | 
| 19 | 
            +
                  ),
         | 
| 20 | 
            +
                ]
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            class CreateTeamGroupStatistic < ActiveRecord::Migration
         | 
| 2 | 
            +
              def change
         | 
| 3 | 
            +
                create_table :team_group_statistics do |t|
         | 
| 4 | 
            +
                  t.references :team, index: true
         | 
| 5 | 
            +
                  t.integer :games_played
         | 
| 6 | 
            +
                  t.integer :games_won
         | 
| 7 | 
            +
                  t.integer :games_lost
         | 
| 8 | 
            +
                  t.integer :games_draw
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  t.timestamps
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
    
        data/spec/dummy/db/schema.rb
    CHANGED
    
    | @@ -11,7 +11,7 @@ | |
| 11 11 | 
             
            #
         | 
| 12 12 | 
             
            # It's strongly recommended that you check this file into your version control system.
         | 
| 13 13 |  | 
| 14 | 
            -
            ActiveRecord::Schema.define(version:  | 
| 14 | 
            +
            ActiveRecord::Schema.define(version: 20140609133855) do
         | 
| 15 15 |  | 
| 16 16 | 
             
              create_table "imports", force: true do |t|
         | 
| 17 17 | 
             
                t.string   "synchronisable_type", null: false
         | 
| @@ -82,6 +82,18 @@ ActiveRecord::Schema.define(version: 20140507140039) do | |
| 82 82 |  | 
| 83 83 | 
             
              add_index "stages", ["tournament_id"], name: "index_stages_on_tournament_id"
         | 
| 84 84 |  | 
| 85 | 
            +
              create_table "team_group_statistics", force: true do |t|
         | 
| 86 | 
            +
                t.integer  "team_id"
         | 
| 87 | 
            +
                t.integer  "games_played"
         | 
| 88 | 
            +
                t.integer  "games_won"
         | 
| 89 | 
            +
                t.integer  "games_lost"
         | 
| 90 | 
            +
                t.integer  "games_draw"
         | 
| 91 | 
            +
                t.datetime "created_at"
         | 
| 92 | 
            +
                t.datetime "updated_at"
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
              add_index "team_group_statistics", ["team_id"], name: "index_team_group_statistics_on_team_id"
         | 
| 96 | 
            +
             | 
| 85 97 | 
             
              create_table "teams", force: true do |t|
         | 
| 86 98 | 
             
                t.string   "name"
         | 
| 87 99 | 
             
                t.string   "country"
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            FactoryGirl.define do
         | 
| 2 | 
            +
              factory :remote_team_group_statistic, class: Hash do
         | 
| 3 | 
            +
                id { generate :team_id }
         | 
| 4 | 
            +
                team_id { id }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                games_played { generate :integer }
         | 
| 7 | 
            +
                games_won { generate :integer }
         | 
| 8 | 
            +
                games_lost { generate :integer }
         | 
| 9 | 
            +
                games_draw { generate :integer }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                initialize_with { attributes }
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe TeamGroupStatistic do
         | 
| 4 | 
            +
              describe 'synchronization' do
         | 
| 5 | 
            +
                context 'sync with no data specified' do
         | 
| 6 | 
            +
                  subject { -> { TeamGroupStatistic.sync } }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  it { is_expected.to change { TeamGroupStatistic.count }.by(2) }
         | 
| 9 | 
            +
                  it { is_expected.to change { Team.count }.by(2) }
         | 
| 10 | 
            +
                  it { is_expected.to change { Player.count }.by(4) }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  it { is_expected.to change { Synchronisable::Import.count }.by(8) }
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: synchronisable
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0 | 
| 4 | 
            +
              version: 1.0.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Vasiliy Yorkin
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2014-06- | 
| 11 | 
            +
            date: 2014-06-09 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: activerecord
         | 
| @@ -302,8 +302,10 @@ files: | |
| 302 302 | 
             
            - lib/synchronisable.rb
         | 
| 303 303 | 
             
            - lib/synchronisable/attribute_mapper.rb
         | 
| 304 304 | 
             
            - lib/synchronisable/context.rb
         | 
| 305 | 
            +
            - lib/synchronisable/controller.rb
         | 
| 305 306 | 
             
            - lib/synchronisable/dsl/associations.rb
         | 
| 306 307 | 
             
            - lib/synchronisable/dsl/associations/association.rb
         | 
| 308 | 
            +
            - lib/synchronisable/dsl/associations/belongs_to.rb
         | 
| 307 309 | 
             
            - lib/synchronisable/dsl/associations/has_many.rb
         | 
| 308 310 | 
             
            - lib/synchronisable/dsl/associations/has_one.rb
         | 
| 309 311 | 
             
            - lib/synchronisable/dsl/macro.rb
         | 
| @@ -313,8 +315,9 @@ files: | |
| 313 315 | 
             
            - lib/synchronisable/error_handler.rb
         | 
| 314 316 | 
             
            - lib/synchronisable/exceptions.rb
         | 
| 315 317 | 
             
            - lib/synchronisable/gateway.rb
         | 
| 318 | 
            +
            - lib/synchronisable/helper/logging.rb
         | 
| 316 319 | 
             
            - lib/synchronisable/input_descriptor.rb
         | 
| 317 | 
            -
            - lib/synchronisable/ | 
| 320 | 
            +
            - lib/synchronisable/input_parser.rb
         | 
| 318 321 | 
             
            - lib/synchronisable/locale/en.yml
         | 
| 319 322 | 
             
            - lib/synchronisable/locale/ru.yml
         | 
| 320 323 | 
             
            - lib/synchronisable/model.rb
         | 
| @@ -324,7 +327,9 @@ files: | |
| 324 327 | 
             
            - lib/synchronisable/synchronizer.rb
         | 
| 325 328 | 
             
            - lib/synchronisable/synchronizers/synchronizer_default.rb
         | 
| 326 329 | 
             
            - lib/synchronisable/version.rb
         | 
| 327 | 
            -
            - lib/synchronisable/worker.rb
         | 
| 330 | 
            +
            - lib/synchronisable/worker/associations.rb
         | 
| 331 | 
            +
            - lib/synchronisable/worker/base.rb
         | 
| 332 | 
            +
            - lib/synchronisable/worker/record.rb
         | 
| 328 333 | 
             
            - spec/dummy/.gitignore
         | 
| 329 334 | 
             
            - spec/dummy/Rakefile
         | 
| 330 335 | 
             
            - spec/dummy/app/assets/images/.keep
         | 
| @@ -336,6 +341,7 @@ files: | |
| 336 341 | 
             
            - spec/dummy/app/gateways/player_gateway.rb
         | 
| 337 342 | 
             
            - spec/dummy/app/gateways/stage_gateway.rb
         | 
| 338 343 | 
             
            - spec/dummy/app/gateways/team_gateway.rb
         | 
| 344 | 
            +
            - spec/dummy/app/gateways/team_group_statistic_gateway.rb
         | 
| 339 345 | 
             
            - spec/dummy/app/gateways/tournament_gateway.rb
         | 
| 340 346 | 
             
            - spec/dummy/app/helpers/application_helper.rb
         | 
| 341 347 | 
             
            - spec/dummy/app/mailers/.keep
         | 
| @@ -346,11 +352,13 @@ files: | |
| 346 352 | 
             
            - spec/dummy/app/models/stadium.rb
         | 
| 347 353 | 
             
            - spec/dummy/app/models/stage.rb
         | 
| 348 354 | 
             
            - spec/dummy/app/models/team.rb
         | 
| 355 | 
            +
            - spec/dummy/app/models/team_group_statistic.rb
         | 
| 349 356 | 
             
            - spec/dummy/app/models/tournament.rb
         | 
| 350 357 | 
             
            - spec/dummy/app/synchronizers/break_convention_team_synchronizer.rb
         | 
| 351 358 | 
             
            - spec/dummy/app/synchronizers/match_synchronizer.rb
         | 
| 352 359 | 
             
            - spec/dummy/app/synchronizers/player_synchronizer.rb
         | 
| 353 360 | 
             
            - spec/dummy/app/synchronizers/stage_synchronizer.rb
         | 
| 361 | 
            +
            - spec/dummy/app/synchronizers/team_group_statistic_synchronizer.rb
         | 
| 354 362 | 
             
            - spec/dummy/app/synchronizers/tournament_synchronizer.rb
         | 
| 355 363 | 
             
            - spec/dummy/app/views/layouts/application.html.erb
         | 
| 356 364 | 
             
            - spec/dummy/bin/bundle
         | 
| @@ -383,6 +391,7 @@ files: | |
| 383 391 | 
             
            - spec/dummy/db/migrate/20140507135800_create_tournaments.rb
         | 
| 384 392 | 
             
            - spec/dummy/db/migrate/20140507135837_create_stages.rb
         | 
| 385 393 | 
             
            - spec/dummy/db/migrate/20140507140039_add_stage_id_to_matches.rb
         | 
| 394 | 
            +
            - spec/dummy/db/migrate/20140609133855_create_team_group_statistic.rb
         | 
| 386 395 | 
             
            - spec/dummy/db/schema.rb
         | 
| 387 396 | 
             
            - spec/dummy/db/seeds.rb
         | 
| 388 397 | 
             
            - spec/dummy/lib/assets/.keep
         | 
| @@ -403,10 +412,12 @@ files: | |
| 403 412 | 
             
            - spec/factories/remote/player.rb
         | 
| 404 413 | 
             
            - spec/factories/remote/stage.rb
         | 
| 405 414 | 
             
            - spec/factories/remote/team.rb
         | 
| 415 | 
            +
            - spec/factories/remote/team_group_statistic.rb
         | 
| 406 416 | 
             
            - spec/factories/remote/tournament.rb
         | 
| 407 417 | 
             
            - spec/factories/stadium.rb
         | 
| 408 418 | 
             
            - spec/factories/team.rb
         | 
| 409 419 | 
             
            - spec/models/match_spec.rb
         | 
| 420 | 
            +
            - spec/models/team_group_statistic_spec.rb
         | 
| 410 421 | 
             
            - spec/models/team_spec.rb
         | 
| 411 422 | 
             
            - spec/spec_helper.rb
         | 
| 412 423 | 
             
            - spec/synchronisable/dsl/macro_spec.rb
         | 
| @@ -455,6 +466,7 @@ test_files: | |
| 455 466 | 
             
            - spec/dummy/app/gateways/player_gateway.rb
         | 
| 456 467 | 
             
            - spec/dummy/app/gateways/stage_gateway.rb
         | 
| 457 468 | 
             
            - spec/dummy/app/gateways/team_gateway.rb
         | 
| 469 | 
            +
            - spec/dummy/app/gateways/team_group_statistic_gateway.rb
         | 
| 458 470 | 
             
            - spec/dummy/app/gateways/tournament_gateway.rb
         | 
| 459 471 | 
             
            - spec/dummy/app/helpers/application_helper.rb
         | 
| 460 472 | 
             
            - spec/dummy/app/mailers/.keep
         | 
| @@ -465,11 +477,13 @@ test_files: | |
| 465 477 | 
             
            - spec/dummy/app/models/stadium.rb
         | 
| 466 478 | 
             
            - spec/dummy/app/models/stage.rb
         | 
| 467 479 | 
             
            - spec/dummy/app/models/team.rb
         | 
| 480 | 
            +
            - spec/dummy/app/models/team_group_statistic.rb
         | 
| 468 481 | 
             
            - spec/dummy/app/models/tournament.rb
         | 
| 469 482 | 
             
            - spec/dummy/app/synchronizers/break_convention_team_synchronizer.rb
         | 
| 470 483 | 
             
            - spec/dummy/app/synchronizers/match_synchronizer.rb
         | 
| 471 484 | 
             
            - spec/dummy/app/synchronizers/player_synchronizer.rb
         | 
| 472 485 | 
             
            - spec/dummy/app/synchronizers/stage_synchronizer.rb
         | 
| 486 | 
            +
            - spec/dummy/app/synchronizers/team_group_statistic_synchronizer.rb
         | 
| 473 487 | 
             
            - spec/dummy/app/synchronizers/tournament_synchronizer.rb
         | 
| 474 488 | 
             
            - spec/dummy/app/views/layouts/application.html.erb
         | 
| 475 489 | 
             
            - spec/dummy/bin/bundle
         | 
| @@ -502,6 +516,7 @@ test_files: | |
| 502 516 | 
             
            - spec/dummy/db/migrate/20140507135800_create_tournaments.rb
         | 
| 503 517 | 
             
            - spec/dummy/db/migrate/20140507135837_create_stages.rb
         | 
| 504 518 | 
             
            - spec/dummy/db/migrate/20140507140039_add_stage_id_to_matches.rb
         | 
| 519 | 
            +
            - spec/dummy/db/migrate/20140609133855_create_team_group_statistic.rb
         | 
| 505 520 | 
             
            - spec/dummy/db/schema.rb
         | 
| 506 521 | 
             
            - spec/dummy/db/seeds.rb
         | 
| 507 522 | 
             
            - spec/dummy/lib/assets/.keep
         | 
| @@ -522,10 +537,12 @@ test_files: | |
| 522 537 | 
             
            - spec/factories/remote/player.rb
         | 
| 523 538 | 
             
            - spec/factories/remote/stage.rb
         | 
| 524 539 | 
             
            - spec/factories/remote/team.rb
         | 
| 540 | 
            +
            - spec/factories/remote/team_group_statistic.rb
         | 
| 525 541 | 
             
            - spec/factories/remote/tournament.rb
         | 
| 526 542 | 
             
            - spec/factories/stadium.rb
         | 
| 527 543 | 
             
            - spec/factories/team.rb
         | 
| 528 544 | 
             
            - spec/models/match_spec.rb
         | 
| 545 | 
            +
            - spec/models/team_group_statistic_spec.rb
         | 
| 529 546 | 
             
            - spec/models/team_spec.rb
         | 
| 530 547 | 
             
            - spec/spec_helper.rb
         | 
| 531 548 | 
             
            - spec/synchronisable/dsl/macro_spec.rb
         | 
| @@ -1,171 +0,0 @@ | |
| 1 | 
            -
            require 'colorize'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            require 'synchronisable/error_handler'
         | 
| 4 | 
            -
            require 'synchronisable/context'
         | 
| 5 | 
            -
            require 'synchronisable/input_dispatcher'
         | 
| 6 | 
            -
            require 'synchronisable/source'
         | 
| 7 | 
            -
            require 'synchronisable/models/import'
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            module Synchronisable
         | 
| 10 | 
            -
              # Responsible for model synchronization.
         | 
| 11 | 
            -
              #
         | 
| 12 | 
            -
              # @api private
         | 
| 13 | 
            -
              class Worker
         | 
| 14 | 
            -
                class << self
         | 
| 15 | 
            -
                  # Creates a new instance of worker and initiates model synchronization.
         | 
| 16 | 
            -
                  #
         | 
| 17 | 
            -
                  # @overload run(model, data, options)
         | 
| 18 | 
            -
                  #   @param model [Class] model class to be synchronized
         | 
| 19 | 
            -
                  #   @param options [Hash] synchronization options
         | 
| 20 | 
            -
                  #   @option options [Hash] :include assocations to be synchronized.
         | 
| 21 | 
            -
                  #     Use this option to override `has_one` & `has_many` assocations
         | 
| 22 | 
            -
                  #     defined in model synchronizer.
         | 
| 23 | 
            -
                  # @overload run(model, data)
         | 
| 24 | 
            -
                  # @overload run(model)
         | 
| 25 | 
            -
                  #
         | 
| 26 | 
            -
                  # @return [Synchronisable::Context] synchronization context
         | 
| 27 | 
            -
                  def run(model, *args)
         | 
| 28 | 
            -
                    options = args.extract_options!
         | 
| 29 | 
            -
                    data = args.first
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                    new(model, options).run(data)
         | 
| 32 | 
            -
                  end
         | 
| 33 | 
            -
                end
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                # Initiates model synchronization.
         | 
| 36 | 
            -
                #
         | 
| 37 | 
            -
                # @param data [Array<Hash>] array of hashes with remote attriutes.
         | 
| 38 | 
            -
                #   If not specified worker will try to get the data
         | 
| 39 | 
            -
                #   using defined gateway class or `fetch` lambda/proc
         | 
| 40 | 
            -
                #   defined in corresponding synchronizer
         | 
| 41 | 
            -
                #
         | 
| 42 | 
            -
                # @return [Synchronisable::Context] synchronization context
         | 
| 43 | 
            -
                #
         | 
| 44 | 
            -
                # @see Synchronisable::InputDispatcher
         | 
| 45 | 
            -
                def run(data)
         | 
| 46 | 
            -
                  sync do |context|
         | 
| 47 | 
            -
                    error_handler = ErrorHandler.new(@logger, context)
         | 
| 48 | 
            -
                    context.before = @model.imports_count
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                    hashes = InputDispatcher.dispatch(@model, @synchronizer, data)
         | 
| 51 | 
            -
                    hashes.each do |attrs|
         | 
| 52 | 
            -
                      source = Source.new(@model, @parent, attrs)
         | 
| 53 | 
            -
                      error_handler.handle(source) do
         | 
| 54 | 
            -
                        @synchronizer.with_sync_callbacks(source) do
         | 
| 55 | 
            -
                          sync_record(source)
         | 
| 56 | 
            -
                          sync_associations(source)
         | 
| 57 | 
            -
                          set_record_foreign_keys(source)
         | 
| 58 | 
            -
                        end
         | 
| 59 | 
            -
                      end
         | 
| 60 | 
            -
                    end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
                    context.after = @model.imports_count
         | 
| 63 | 
            -
                    context.deleted = 0
         | 
| 64 | 
            -
                  end
         | 
| 65 | 
            -
                end
         | 
| 66 | 
            -
             | 
| 67 | 
            -
                private
         | 
| 68 | 
            -
             | 
| 69 | 
            -
                def initialize(model, options)
         | 
| 70 | 
            -
                  @model, @synchronizer = model, model.synchronizer
         | 
| 71 | 
            -
                  @logger = @synchronizer.logger
         | 
| 72 | 
            -
                  @parent = options[:parent]
         | 
| 73 | 
            -
                end
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                def sync
         | 
| 76 | 
            -
                  @logger.progname = "#{@model} synchronization"
         | 
| 77 | 
            -
                  log_info('STARTING', :yellow, true)
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                  context = Context.new(@model, @parent.try(:model))
         | 
| 80 | 
            -
                  yield context
         | 
| 81 | 
            -
             | 
| 82 | 
            -
                  log_info('DONE', :yellow, true)
         | 
| 83 | 
            -
                  log_info(context.summary_message, :cyan, true)
         | 
| 84 | 
            -
             | 
| 85 | 
            -
                  context
         | 
| 86 | 
            -
                end
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                # Method called by {#run} for each remote model attribute hash
         | 
| 89 | 
            -
                #
         | 
| 90 | 
            -
                # @param source [Synchronisable::Source] synchronization source
         | 
| 91 | 
            -
                #
         | 
| 92 | 
            -
                # @return [Boolean] `true` if synchronization was completed
         | 
| 93 | 
            -
                #   without errors, `false` otherwise
         | 
| 94 | 
            -
                def sync_record(source)
         | 
| 95 | 
            -
                  @synchronizer.with_record_sync_callbacks(source) do
         | 
| 96 | 
            -
                    source.prepare
         | 
| 97 | 
            -
             | 
| 98 | 
            -
                    log_info(source.dump_message, :green)
         | 
| 99 | 
            -
             | 
| 100 | 
            -
                    if source.updatable?
         | 
| 101 | 
            -
                      log_info("updating #{@model}: #{source.local_record.id}", :blue)
         | 
| 102 | 
            -
                      source.update_record
         | 
| 103 | 
            -
                    else
         | 
| 104 | 
            -
                      source.create_record_pair
         | 
| 105 | 
            -
                      log_info("#{@model} (id: #{source.local_record.id}) was created", :blue)
         | 
| 106 | 
            -
                      log_info("#{source.import_record.class}: #{source.import_record.id} was created", :blue)
         | 
| 107 | 
            -
                    end
         | 
| 108 | 
            -
                  end
         | 
| 109 | 
            -
                end
         | 
| 110 | 
            -
             | 
| 111 | 
            -
                def set_record_foreign_keys(source)
         | 
| 112 | 
            -
                  reflection = belongs_to_parent_reflection
         | 
| 113 | 
            -
                  return unless reflection
         | 
| 114 | 
            -
             | 
| 115 | 
            -
                  belongs_to_key = "#{reflection.plural_name.singularize}_id"
         | 
| 116 | 
            -
                  source.local_record.update_attributes!(
         | 
| 117 | 
            -
                    belongs_to_key => @parent.local_record.id
         | 
| 118 | 
            -
                  )
         | 
| 119 | 
            -
                end
         | 
| 120 | 
            -
             | 
| 121 | 
            -
                # Synchronizes associations.
         | 
| 122 | 
            -
                #
         | 
| 123 | 
            -
                # @param source [Synchronisable::Source] synchronization source
         | 
| 124 | 
            -
                #
         | 
| 125 | 
            -
                # @see Synchronisable::DSL::Associations
         | 
| 126 | 
            -
                # @see Synchronisable::DSL::Associations::Association
         | 
| 127 | 
            -
                def sync_associations(source)
         | 
| 128 | 
            -
                  log_info("starting associations sync", :blue) if source.associations.present?
         | 
| 129 | 
            -
             | 
| 130 | 
            -
                  source.associations.each do |association, ids|
         | 
| 131 | 
            -
                    ids.each { |id| sync_association(source, id, association) }
         | 
| 132 | 
            -
                  end
         | 
| 133 | 
            -
                end
         | 
| 134 | 
            -
             | 
| 135 | 
            -
                def sync_association(source, id, association)
         | 
| 136 | 
            -
                  log_info("synchronizing association with id: #{id}", :blue)
         | 
| 137 | 
            -
             | 
| 138 | 
            -
                  @synchronizer.with_association_sync_callbacks(source, id, association) do
         | 
| 139 | 
            -
                    attrs = association.model.synchronizer.find(id)
         | 
| 140 | 
            -
                    Worker.run(association.model, [attrs], { :parent => source })
         | 
| 141 | 
            -
                  end
         | 
| 142 | 
            -
                end
         | 
| 143 | 
            -
             | 
| 144 | 
            -
                # Finds a `belongs_to` reflection to the parent model.
         | 
| 145 | 
            -
                #
         | 
| 146 | 
            -
                # @see ActiveRecord::Reflection::AssociationReflection
         | 
| 147 | 
            -
                def belongs_to_parent_reflection
         | 
| 148 | 
            -
                  return unless @parent
         | 
| 149 | 
            -
             | 
| 150 | 
            -
                  model_reflections.find do |r|
         | 
| 151 | 
            -
                    r.macro == :belongs_to &&
         | 
| 152 | 
            -
                    r.plural_name == @parent.model.table_name
         | 
| 153 | 
            -
                  end
         | 
| 154 | 
            -
                end
         | 
| 155 | 
            -
             | 
| 156 | 
            -
                def model_reflections
         | 
| 157 | 
            -
                  @model.reflections.values
         | 
| 158 | 
            -
                end
         | 
| 159 | 
            -
             | 
| 160 | 
            -
                def log_info(msg, color = :white, force = true)
         | 
| 161 | 
            -
                  text = msg.colorize(color) if colorize_logging?
         | 
| 162 | 
            -
                  @logger.info(text)         if force || verbose_logging?
         | 
| 163 | 
            -
                end
         | 
| 164 | 
            -
             | 
| 165 | 
            -
                %i(verbose colorize).each do |name|
         | 
| 166 | 
            -
                  define_method("#{name}_logging?".to_sym) do
         | 
| 167 | 
            -
                    Synchronisable.logging[name]
         | 
| 168 | 
            -
                  end
         | 
| 169 | 
            -
                end
         | 
| 170 | 
            -
              end
         | 
| 171 | 
            -
            end
         |