synchronisable 0.0.3 → 0.0.4

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.
@@ -1,4 +1,5 @@
1
1
  require 'synchronisable/dsl/macro'
2
+ require 'synchronisable/attribute_mapper'
2
3
  require 'synchronisable/dsl/associations'
3
4
  require 'synchronisable/exceptions'
4
5
 
@@ -35,11 +36,7 @@ module Synchronisable
35
36
 
36
37
  # Logger that will be used during synchronization
37
38
  # of this particular model.
38
- # Fallbacks to `Rails.logger` if available, otherwise
39
- # `STDOUT` will be used for output.
40
- attribute :logger, default: -> {
41
- defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
42
- }
39
+ attribute :logger, default: -> { Synchronisable.logging[:logger] }
43
40
 
44
41
  # Lambda that returns array of hashes with remote attributes.
45
42
  method :fetch, default: -> { [] }
@@ -110,7 +107,6 @@ module Synchronisable
110
107
  def extract_remote_id(attrs)
111
108
  id = attrs.delete(remote_id)
112
109
  ensure_remote_id(id)
113
- id
114
110
  end
115
111
 
116
112
  # Maps the remote attributes to local model attributes.
@@ -120,11 +116,11 @@ module Synchronisable
120
116
  #
121
117
  # @api private
122
118
  def map_attributes(attrs)
123
- result = attrs.dup
124
- apply_mappings(result) if mappings.present?
125
- apply_only_filter(result) if only.present?
126
- apply_except_filter(result) if except.present?
127
- result
119
+ AttributeMapper.map(attrs, mappings, {
120
+ :only => only,
121
+ :except => except,
122
+ :keep => associations.keys
123
+ })
128
124
  end
129
125
 
130
126
  %w(sync record_sync association_sync).each do |method|
@@ -135,21 +131,6 @@ module Synchronisable
135
131
 
136
132
  private
137
133
 
138
- def apply_mappings(attrs)
139
- attrs.transform_keys! { |key| mappings[key] || key }
140
- end
141
-
142
- def apply_only_filter(attrs)
143
- attrs.keep_if do |key|
144
- only.include?(key) ||
145
- associations.keys.include?(key)
146
- end
147
- end
148
-
149
- def apply_except_filter(attrs)
150
- attrs.delete_if { |key| key.nil? || except.include?(key) }
151
- end
152
-
153
134
  def run_callbacks(method, args, block)
154
135
  before = send(:"before_#{method}")
155
136
  after = send(:"after_#{method}")
@@ -165,12 +146,11 @@ module Synchronisable
165
146
  #
166
147
  # @raise [MissedRemoteIdError] raised when data doesn't contain remote id
167
148
  def ensure_remote_id(id)
168
- if id.blank?
169
- raise MissedRemoteIdError, I18n.t(
170
- 'errors.missed_remote_id',
171
- remote_id: remote_id
172
- )
173
- end
149
+ return id if id.present?
150
+ raise MissedRemoteIdError, I18n.t(
151
+ 'errors.missed_remote_id',
152
+ remote_id: remote_id
153
+ )
174
154
  end
175
155
  end
176
156
  end
@@ -2,7 +2,7 @@ module Synchronisable
2
2
  module VERSION
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- PATCH = 3
5
+ PATCH = 4
6
6
  SUFFIX = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, PATCH, SUFFIX].compact.join('.')
@@ -1,5 +1,8 @@
1
+ require 'colorize'
2
+
1
3
  require 'synchronisable/error_handler'
2
4
  require 'synchronisable/context'
5
+ require 'synchronisable/data_builder'
3
6
  require 'synchronisable/source'
4
7
  require 'synchronisable/models/import'
5
8
 
@@ -41,11 +44,8 @@ module Synchronisable
41
44
  error_handler = ErrorHandler.new(@logger, context)
42
45
  context.before = @model.imports_count
43
46
 
44
- data = @synchronizer.fetch.() if data.blank?
45
- data.each do |attrs|
46
- # TODO: Handle case when only array of ids is given
47
- # What to do with associations?
48
-
47
+ hashes = DataBuilder.build(@model, @synchronizer, data)
48
+ hashes.each do |attrs|
49
49
  source = Source.new(@model, @parent, attrs)
50
50
  error_handler.handle(source) do
51
51
  @synchronizer.with_sync_callbacks(source) do
@@ -71,20 +71,17 @@ module Synchronisable
71
71
 
72
72
  def sync
73
73
  @logger.progname = "#{@model} synchronization"
74
- @logger.info 'starting'
74
+ log_info('STARTING', :yellow, true)
75
75
 
76
76
  context = Context.new(@model, @parent.try(:model))
77
77
  yield context
78
78
 
79
- @logger.info 'done'
80
- @logger.info(context.summary_message)
81
- @logger.progname = nil
79
+ log_info('DONE', :yellow, true)
80
+ log_info(context.summary_message, :cyan, true)
82
81
 
83
82
  context
84
83
  end
85
84
 
86
- # TODO: Think about how to move it from here to Source or some other place
87
-
88
85
  # Method called by {#run} for each remote model attribute hash
89
86
  #
90
87
  # @param source [Synchronisable::Source] synchronization source
@@ -93,44 +90,21 @@ module Synchronisable
93
90
  # without errors, `false` otherwise
94
91
  def sync_record(source)
95
92
  @synchronizer.with_record_sync_callbacks(source) do
96
- source.build
93
+ source.prepare
97
94
 
98
- @logger.info(source.dump_message) if verbose_logging?
95
+ log_info(source.dump_message, :green)
99
96
 
100
97
  if source.updatable?
101
- update_record(source)
98
+ log_info("updating #{@model}: #{source.local_record.id}", :blue)
99
+ source.update_record
102
100
  else
103
- create_record_pair(source)
101
+ source.create_record_pair
102
+ log_info("#{@model} (id: #{source.local_record.id}) was created", :blue)
103
+ log_info("#{source.import_record.class}: #{source.import_record.id} was created", :blue)
104
104
  end
105
105
  end
106
106
  end
107
107
 
108
- def update_record(source)
109
- if verbose_logging?
110
- @logger.info "updating #{@model}: #{source.local_record.id}"
111
- end
112
-
113
- # TODO: Напрашивается, да?
114
- source.local_record.update_attributes!(source.local_attrs)
115
- end
116
-
117
- def create_record_pair(source)
118
- local_record = @model.create!(source.local_attrs)
119
- import_record = Import.create!(
120
- :synchronisable_id => local_record.id,
121
- :synchronisable_type => @model.to_s,
122
- :remote_id => source.remote_id,
123
- :attrs => source.local_attrs
124
- )
125
-
126
- source.import_record = import_record
127
-
128
- if verbose_logging?
129
- @logger.info "#{@model}: #{local_record.id} was created"
130
- @logger.info "#{import_record.class}: #{import_record.id} was created"
131
- end
132
- end
133
-
134
108
  def set_record_foreign_keys(source)
135
109
  reflection = belongs_to_parent_reflection
136
110
  return unless reflection
@@ -148,9 +122,7 @@ module Synchronisable
148
122
  # @see Synchronisable::DSL::Associations
149
123
  # @see Synchronisable::DSL::Associations::Association
150
124
  def sync_associations(source)
151
- if verbose_logging? && source.associations.present?
152
- @logger.info "starting associations sync"
153
- end
125
+ log_info("starting associations sync", :blue) if source.associations.present?
154
126
 
155
127
  source.associations.each do |association, ids|
156
128
  ids.each { |id| sync_association(source, id, association) }
@@ -158,9 +130,7 @@ module Synchronisable
158
130
  end
159
131
 
160
132
  def sync_association(source, id, association)
161
- if verbose_logging?
162
- @logger.info "synchronizing association with id: #{id}"
163
- end
133
+ log_info("synchronizing association with id: #{id}", :blue)
164
134
 
165
135
  @synchronizer.with_association_sync_callbacks(source, id, association) do
166
136
  attrs = association.model.synchronizer.find.(id)
@@ -184,8 +154,15 @@ module Synchronisable
184
154
  @model.reflections.values
185
155
  end
186
156
 
187
- def verbose_logging?
188
- Synchronisable.logging[:verbose]
157
+ def log_info(msg, color = :white, force = true)
158
+ text = msg.colorize(color) if colorize_logging?
159
+ @logger.info(text) if force || verbose_logging?
160
+ end
161
+
162
+ %i(verbose colorize).each do |name|
163
+ define_method("#{name}_logging?".to_sym) do
164
+ Synchronisable.logging[name]
165
+ end
189
166
  end
190
167
  end
191
168
  end
@@ -48,7 +48,13 @@ module Synchronisable
48
48
  {}
49
49
  end
50
50
  config_accessor :logging do
51
+ default_logger = -> { Logger.new(STDOUT) }
52
+ rails_logger = -> { Rails.logger || default_logger.() }
53
+
54
+ logger = defined?(Rails) ? rails_logger.() : default_logger.()
55
+
51
56
  {
57
+ :logger => logger,
52
58
  :verbose => true,
53
59
  :colorize => true
54
60
  }
@@ -59,7 +65,7 @@ module Synchronisable
59
65
  # @param models [Array] array of models that should be synchronized.
60
66
  # This take a precedence over models defined in {Synchronisable#models}.
61
67
  # If this parameter is not specified and {Synchronisable#models} is empty,
62
- # than it will try to sync only those models which have a corresponding synchronizers.
68
+ # than it will try to sync only those models which have a corresponding synchronizers
63
69
  #
64
70
  # @return [Array<[Synchronisable::Context]>] array of synchronization contexts
65
71
  #
@@ -1,6 +1,8 @@
1
1
  class BreakConventionTeamSynchronizer < Synchronisable::Synchronizer
2
2
  @gateway = TeamGateway.new
3
3
 
4
+ has_many :players
5
+
4
6
  remote_id :maet_id
5
7
  mappings(
6
8
  :eman => :name,
@@ -4,8 +4,6 @@ class MatchSynchronizer < Synchronisable::Synchronizer
4
4
  has_one :team, key: 'home_team_id'
5
5
  has_one :team, key: 'away_team_id'
6
6
 
7
- has_many :players
8
-
9
7
  remote_id :match_id
10
8
 
11
9
  mappings(
@@ -6,7 +6,6 @@ require 'action_mailer/railtie'
6
6
  require 'action_view/railtie'
7
7
  require 'sprockets/railtie'
8
8
 
9
- Bundler.require(*Rails.groups)
10
9
  require 'synchronisable'
11
10
 
12
11
  module Dummy
@@ -1,5 +1,5 @@
1
1
  FactoryGirl.define do
2
2
  factory :import, class: Synchronisable::Import do
3
- remote_id { generate :integer }
3
+ remote_id { generate :remote_id }
4
4
  end
5
5
  end
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require 'pry-byebug'
2
3
 
3
4
  describe Team do
4
5
  describe 'synchronization' do
@@ -36,7 +37,91 @@ describe Team do
36
37
  it { is_expected.to change { Synchronisable::Import.count }.by(6) }
37
38
  end
38
39
 
39
- context 'when remote id is not specified' do
40
+ describe 'restrict sync to specific record(s)' do
41
+ context 'when remote id is specified' do
42
+ context "when local record doesn't exist" do
43
+ subject { -> { Team.sync('team_0') } }
44
+
45
+ it { is_expected.to change { Team.count }.by(1) }
46
+ it { is_expected.to change { Synchronisable::Import.count }.by(3) }
47
+ end
48
+
49
+ context 'when local record exists' do
50
+ include_context 'team_0 import'
51
+
52
+ subject do
53
+ -> {
54
+ Team.sync('team_0')
55
+ team_0.reload
56
+ }
57
+ end
58
+
59
+ it { is_expected.not_to change { Team.count } }
60
+
61
+ it { is_expected.to change { Player.count }.by(2) }
62
+ it { is_expected.to change { Synchronisable::Import.count }.by(2) }
63
+
64
+ it { is_expected.to change { team_0.name } }
65
+ it { is_expected.to change { team_0.country } }
66
+ it { is_expected.to change { team_0.city } }
67
+ end
68
+ end
69
+
70
+ context 'when there is 2 imports with corresponding local records' do
71
+ include_context 'team imports'
72
+
73
+ context 'when local id is specified' do
74
+ subject do
75
+ -> {
76
+ Team.sync(team_0.id)
77
+ team_0.reload
78
+ }
79
+ end
80
+
81
+ it { is_expected.not_to change { Team.count } }
82
+
83
+ it { is_expected.to change { Player.count }.by(2) }
84
+ it { is_expected.to change { Synchronisable::Import.count }.by(2) }
85
+
86
+ it { is_expected.to change { team_0.name } }
87
+ it { is_expected.to change { team_0.country } }
88
+ it { is_expected.to change { team_0.city } }
89
+ end
90
+
91
+ context 'when array of local ids is specified' do
92
+ subject do
93
+ -> {
94
+ Team.sync([team_0.id, team_1.id])
95
+ [team_0, team_1].each(&:reload)
96
+ }
97
+ end
98
+
99
+ it { is_expected.not_to change { Team.count } }
100
+
101
+ it { is_expected.to change { Player.count }.by(4) }
102
+ it { is_expected.to change { Synchronisable::Import.count }.by(4) }
103
+
104
+ it { is_expected.to change { team_0.name } }
105
+ it { is_expected.to change { team_0.country } }
106
+ it { is_expected.to change { team_0.city } }
107
+
108
+ it { is_expected.to change { team_1.name } }
109
+ it { is_expected.to change { team_1.country } }
110
+ it { is_expected.to change { team_1.city } }
111
+ end
112
+
113
+ context 'when array of remote ids is specified' do
114
+ subject do
115
+ -> {
116
+ Team.sync([import_0.remote_id, import_1.remote_id])
117
+ [team_0, team_1].each(&:reload)
118
+ }
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ context 'when remote id is not specified in attributes hash' do
40
125
  subject { Team.sync([remote_attrs.last]) }
41
126
 
42
127
  its(:errors) { should have(1).items }
data/spec/spec_helper.rb CHANGED
@@ -64,8 +64,8 @@ Spork.prefork do
64
64
  end
65
65
 
66
66
  FactoryGirl.define do
67
- %w(match team player match_player stage tournament).each do |model|
68
- sequence(:"#{model}_id") { |n| "#{model}_#{n}" }
67
+ %w(remote match team player match_player stage tournament).each do |prefix|
68
+ sequence(:"#{prefix}_id") { |n| "#{prefix}_#{n}" }
69
69
  end
70
70
  end
71
71
  end
@@ -0,0 +1,33 @@
1
+ shared_context 'team_0 import' do
2
+ let!(:import_0) do
3
+ create(:import,
4
+ :remote_id => 'team_0',
5
+ :synchronisable => create(:team,
6
+ :name => 'x',
7
+ :country => 'Russia',
8
+ :city => 'Moscow',
9
+ )
10
+ )
11
+ end
12
+ let!(:team_0) { import_0.synchronisable }
13
+ end
14
+
15
+ shared_context 'team_1 import' do
16
+ let!(:import_1) do
17
+ create(:import,
18
+ :remote_id => 'team_1',
19
+ :synchronisable => create(:team,
20
+ :name => 'y',
21
+ :country => 'France',
22
+ :city => 'Paris',
23
+ )
24
+ )
25
+ end
26
+ let!(:team_1) { import_1.synchronisable }
27
+ end
28
+
29
+ shared_context 'team imports' do
30
+ include_context 'team_0 import'
31
+ include_context 'team_1 import'
32
+ end
33
+
File without changes
@@ -21,13 +21,13 @@ Gem::Specification.new do |spec|
21
21
  spec.add_runtime_dependency "activerecord", ">= 3.0.0"
22
22
  spec.add_runtime_dependency "activesupport", ">= 3.0.0"
23
23
  spec.add_runtime_dependency "i18n"
24
+ spec.add_runtime_dependency "colorize"
24
25
 
25
26
  spec.add_development_dependency "bundler", "~> 1.5"
26
27
  spec.add_development_dependency "rake"
27
28
  spec.add_development_dependency "yard"
28
29
  spec.add_development_dependency "rails", "~> 4.0.0"
29
30
  spec.add_development_dependency "rspec-rails", "~> 3.0.0.beta2"
30
- # spec.add_development_dependency "rspec-given"
31
31
  spec.add_development_dependency "rspec-its"
32
32
  spec.add_development_dependency "sqlite3"
33
33
  spec.add_development_dependency "factory_girl"