synchronisable 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +2 -0
  5. data/.travis.yml +4 -0
  6. data/Gemfile +9 -0
  7. data/Guardfile +14 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +22 -0
  10. data/Rakefile +7 -0
  11. data/TODO.md +20 -0
  12. data/bin/autospec +16 -0
  13. data/bin/rake +16 -0
  14. data/bin/rspec +16 -0
  15. data/lib/generators/synchronizable/USAGE +2 -0
  16. data/lib/generators/synchronizable/install_generator.rb +24 -0
  17. data/lib/generators/synchronizable/templates/create_imports_migration.rb +21 -0
  18. data/lib/generators/synchronizable/templates/initializer.rb +14 -0
  19. data/lib/synchronizable.rb +92 -0
  20. data/lib/synchronizable/context.rb +25 -0
  21. data/lib/synchronizable/dsl/associations.rb +57 -0
  22. data/lib/synchronizable/dsl/associations/association.rb +59 -0
  23. data/lib/synchronizable/dsl/associations/has_many.rb +12 -0
  24. data/lib/synchronizable/dsl/associations/has_one.rb +13 -0
  25. data/lib/synchronizable/dsl/macro.rb +100 -0
  26. data/lib/synchronizable/dsl/macro/attribute.rb +41 -0
  27. data/lib/synchronizable/dsl/macro/expression.rb +51 -0
  28. data/lib/synchronizable/dsl/macro/method.rb +15 -0
  29. data/lib/synchronizable/error_handler.rb +50 -0
  30. data/lib/synchronizable/exceptions.rb +8 -0
  31. data/lib/synchronizable/locale/en.yml +17 -0
  32. data/lib/synchronizable/locale/ru.yml +17 -0
  33. data/lib/synchronizable/model.rb +54 -0
  34. data/lib/synchronizable/model/methods.rb +55 -0
  35. data/lib/synchronizable/models/import.rb +24 -0
  36. data/lib/synchronizable/source.rb +72 -0
  37. data/lib/synchronizable/synchronizer.rb +177 -0
  38. data/lib/synchronizable/synchronizers/synchronizer_default.rb +10 -0
  39. data/lib/synchronizable/version.rb +10 -0
  40. data/lib/synchronizable/worker.rb +191 -0
  41. data/spec/dummy/.gitignore +16 -0
  42. data/spec/dummy/Rakefile +6 -0
  43. data/spec/dummy/app/assets/images/.keep +0 -0
  44. data/spec/dummy/app/assets/javascripts/application.js +16 -0
  45. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  46. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  47. data/spec/dummy/app/gateways/gateway_base.rb +19 -0
  48. data/spec/dummy/app/gateways/match_gateway.rb +15 -0
  49. data/spec/dummy/app/gateways/player_gateway.rb +26 -0
  50. data/spec/dummy/app/gateways/stage_gateway.rb +18 -0
  51. data/spec/dummy/app/gateways/team_gateway.rb +18 -0
  52. data/spec/dummy/app/gateways/tournament_gateway.rb +15 -0
  53. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  54. data/spec/dummy/app/mailers/.keep +0 -0
  55. data/spec/dummy/app/models/.keep +0 -0
  56. data/spec/dummy/app/models/match.rb +10 -0
  57. data/spec/dummy/app/models/match_player.rb +15 -0
  58. data/spec/dummy/app/models/player.rb +5 -0
  59. data/spec/dummy/app/models/stadium.rb +7 -0
  60. data/spec/dummy/app/models/stage.rb +6 -0
  61. data/spec/dummy/app/models/team.rb +5 -0
  62. data/spec/dummy/app/models/tournament.rb +7 -0
  63. data/spec/dummy/app/synchronizers/break_convention_team_synchronizer.rb +14 -0
  64. data/spec/dummy/app/synchronizers/match_synchronizer.rb +71 -0
  65. data/spec/dummy/app/synchronizers/player_synchronizer.rb +19 -0
  66. data/spec/dummy/app/synchronizers/stage_synchronizer.rb +20 -0
  67. data/spec/dummy/app/synchronizers/tournament_synchronizer.rb +20 -0
  68. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  69. data/spec/dummy/bin/bundle +3 -0
  70. data/spec/dummy/bin/rails +4 -0
  71. data/spec/dummy/bin/rake +4 -0
  72. data/spec/dummy/config.ru +4 -0
  73. data/spec/dummy/config/application.rb +26 -0
  74. data/spec/dummy/config/boot.rb +6 -0
  75. data/spec/dummy/config/database.yml +25 -0
  76. data/spec/dummy/config/environment.rb +5 -0
  77. data/spec/dummy/config/environments/development.rb +33 -0
  78. data/spec/dummy/config/environments/production.rb +80 -0
  79. data/spec/dummy/config/environments/test.rb +36 -0
  80. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  81. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  82. data/spec/dummy/config/initializers/inflections.rb +20 -0
  83. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  84. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  85. data/spec/dummy/config/initializers/session_store.rb +3 -0
  86. data/spec/dummy/config/initializers/synchronizable.rb +2 -0
  87. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  88. data/spec/dummy/config/locales/en.yml +23 -0
  89. data/spec/dummy/config/routes.rb +56 -0
  90. data/spec/dummy/db/migrate/20140422132431_create_teams.rb +11 -0
  91. data/spec/dummy/db/migrate/20140422132544_create_matches.rb +12 -0
  92. data/spec/dummy/db/migrate/20140422132708_create_players.rb +15 -0
  93. data/spec/dummy/db/migrate/20140422133122_create_match_players.rb +12 -0
  94. data/spec/dummy/db/migrate/20140422135244_create_imports.rb +21 -0
  95. data/spec/dummy/db/migrate/20140422140817_create_stadiums.rb +10 -0
  96. data/spec/dummy/db/migrate/20140507135800_create_tournaments.rb +13 -0
  97. data/spec/dummy/db/migrate/20140507135837_create_stages.rb +13 -0
  98. data/spec/dummy/db/migrate/20140507140039_add_stage_id_to_matches.rb +6 -0
  99. data/spec/dummy/db/schema.rb +103 -0
  100. data/spec/dummy/db/seeds.rb +7 -0
  101. data/spec/dummy/lib/assets/.keep +0 -0
  102. data/spec/dummy/lib/tasks/.keep +0 -0
  103. data/spec/dummy/log/.keep +0 -0
  104. data/spec/dummy/public/404.html +58 -0
  105. data/spec/dummy/public/422.html +58 -0
  106. data/spec/dummy/public/500.html +57 -0
  107. data/spec/dummy/public/favicon.ico +0 -0
  108. data/spec/dummy/public/robots.txt +5 -0
  109. data/spec/dummy/vendor/assets/javascripts/.keep +0 -0
  110. data/spec/dummy/vendor/assets/stylesheets/.keep +0 -0
  111. data/spec/factories/import.rb +5 -0
  112. data/spec/factories/match.rb +9 -0
  113. data/spec/factories/match_player.rb +9 -0
  114. data/spec/factories/player.rb +10 -0
  115. data/spec/factories/remote/match.rb +21 -0
  116. data/spec/factories/remote/player.rb +18 -0
  117. data/spec/factories/remote/stage.rb +26 -0
  118. data/spec/factories/remote/team.rb +21 -0
  119. data/spec/factories/remote/tournament.rb +18 -0
  120. data/spec/factories/stadium.rb +7 -0
  121. data/spec/factories/team.rb +16 -0
  122. data/spec/models/match_spec.rb +41 -0
  123. data/spec/models/team_spec.rb +85 -0
  124. data/spec/spec_helper.rb +64 -0
  125. data/spec/synchronizable/dsl/macro_spec.rb +59 -0
  126. data/spec/synchronizable/models/import_spec.rb +10 -0
  127. data/spec/synchronizable/support/has_macro.rb +12 -0
  128. data/spec/synchronizable/support/has_macro_subclass.rb +9 -0
  129. data/spec/synchronizable/synchronizable_spec.rb +60 -0
  130. data/synchronizable.gemspec +39 -0
  131. metadata +506 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1c6231d919aeea7c551da1351dba4db462798844
4
+ data.tar.gz: ec360d12bced3a06caaa0a7ab9d1e14ab0c04f98
5
+ SHA512:
6
+ metadata.gz: a5a2d9ac8417ffd54525181be9e0e14115c45db9bec1782a2b2b2e92be55c1b39540d0a050eacf5f18ed55b794ca8878d5e0e276ff61e64d08da1671809fa63e
7
+ data.tar.gz: 3872b79375882a55d5b4cbc5631b2dc2f96413047e18c0e09692a149242dcfa6b137e925755399b580bf3250f7dcd23cb90a21af65b2cf459c23a5cdd652dbc8
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .tags
19
+ .tags_sorted_by_file
20
+ tags
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format progress
3
+ --format Nc
data/.ruby-version ADDED
@@ -0,0 +1,2 @@
1
+ 2.1.0
2
+
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.0
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'pry'
6
+ gem 'byebug'
7
+ gem 'pry-byebug'
8
+
9
+ gem 'rspec-nc' if RUBY_PLATFORM =~ /darwin/
data/Guardfile ADDED
@@ -0,0 +1,14 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec, :all_on_start => false, :all_after_pass => false do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
10
+
11
+ guard 'spork' do
12
+ watch('Gemfile.lock')
13
+ watch('spec/spec_helper.rb') { :rspec }
14
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Vasiliy Yorkin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,22 @@
1
+ [![Build Status](https://travis-ci.org/vyorkin/synchronizable.png?branch=master)](https://travis-ci.org/vyorkin/synchronizable)
2
+
3
+ # Synchronizable
4
+
5
+ Provides base fuctionality (models, DSL) for AR synchronization with external resources (apis, services etc)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'synchronizable'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install synchronizable
20
+
21
+ ## Usage
22
+
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
7
+ task test: :spec
data/TODO.md ADDED
@@ -0,0 +1,20 @@
1
+ Primary objectives
2
+ ======================================
3
+ * general tests (DONE)
4
+ * except/only (DONE)
5
+ * sync method (DONE)
6
+ * dependent syncronization & mapping (DONE)
7
+ * tests for Synchronizable.sync (DONE)
8
+ * destroy_missed
9
+ * worker.rb refactoring
10
+ * integrate with travis, stillmaintained, gemnasium
11
+ * write a good README
12
+ * extended interface
13
+ * sync with include
14
+ * sync with ids array
15
+ * sync method for collection proxy (Model.where(condition).sync)
16
+
17
+ Secondary objectives
18
+ ======================================
19
+ * option for verbose logging (DONE)
20
+ * colorized STDOUT
data/bin/autospec ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'autospec' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rspec-core', 'autospec')
data/bin/rake ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rake' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rake', 'rake')
data/bin/rspec ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rspec' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rspec-core', 'rspec')
@@ -0,0 +1,2 @@
1
+ Description:
2
+ Generates (but does not run) a migration to add an imports table.
@@ -0,0 +1,24 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+ require 'rails/generators/active_record'
4
+
5
+ module Synchronizable
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+
9
+ source_root File.expand_path('../templates', __FILE__)
10
+ desc 'Generates (but does not run) a migration to add an imports table.'
11
+
12
+ def create_migration_file
13
+ migration_template 'create_imports_migration.rb', 'db/migrate/create_imports.rb'
14
+ end
15
+
16
+ def create_initializer_file
17
+ copy_file 'initializer.rb', 'config/initializers/synchronizable.rb'
18
+ end
19
+
20
+ def self.next_migration_number(dirname)
21
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ class CreateImports < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :imports do |t|
4
+ t.string :synchronizable_type, null: false
5
+ t.integer :synchronizable_id, null: false
6
+ t.text :attrs
7
+ t.string :remote_id, null: false
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :imports, :remote_id
13
+ add_index :imports, [:synchronizable_type, :synchronizable_id]
14
+ end
15
+
16
+ def self.down
17
+ remove_index :imports, :remote_id
18
+ remove_index :imports, [:synchronizable_type, :synchronizable_id]
19
+ drop_table :imports
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ Synchronizable.configure do |config|
2
+ # Logging configuration
3
+ #
4
+ # config.logging = {
5
+ # :verbose => true,
6
+ # :colorize => true
7
+ # }
8
+
9
+ # If you want to restrict synchronized models.
10
+ # By default it will try to sync all models that have
11
+ # a `synchronizable` dsl instruction.
12
+ #
13
+ # config.models = %w(Foo Bar)
14
+ end
@@ -0,0 +1,92 @@
1
+ require 'active_record'
2
+
3
+ require 'active_support/core_ext/hash'
4
+ require 'active_support/core_ext/class/attribute'
5
+ require 'active_support/core_ext/object/try'
6
+ require 'active_support/core_ext/object/deep_dup'
7
+ require 'active_support/core_ext/string/inflections'
8
+ require 'active_support/configurable'
9
+ require 'active_support/concern'
10
+
11
+ require 'i18n'
12
+
13
+ require 'synchronizable/version'
14
+ require 'synchronizable/models/import'
15
+ require 'synchronizable/synchronizer'
16
+ require 'synchronizable/model'
17
+
18
+ locale_paths = File.join(File.dirname(__FILE__),
19
+ 'synchronizable', 'locale', '*.yml')
20
+
21
+ Dir[locale_paths].each { |path| I18n.load_path << path }
22
+ I18n.backend.load_translations unless defined?(Rails)
23
+
24
+ I18n.config.enforce_available_locales = true
25
+ I18n.default_locale = :en
26
+ I18n.available_locales = [:en, :ru]
27
+
28
+ # The desired interface:
29
+ # ><(((*>
30
+ #
31
+ # + Model.sync
32
+ # + Model.sync([{},...])
33
+ #
34
+ # Match.sync(:include => {
35
+ # :match_players => :player
36
+ # })
37
+ # Model.sync([id1, ..., idn])
38
+ #
39
+ # Model.where(condition).sync
40
+ # Match.where(condition).sync(:include => {
41
+ # :match_players => :player
42
+ # })
43
+
44
+ module Synchronizable
45
+ include ActiveSupport::Configurable
46
+
47
+ config_accessor :models do
48
+ {}
49
+ end
50
+ config_accessor :logging do
51
+ {
52
+ :verbose => true,
53
+ :colorize => true
54
+ }
55
+ end
56
+
57
+ # Syncs models that is defined in {Synchronizable#models}
58
+ #
59
+ # @param models [Array] array of models that should be synchronized.
60
+ # This take a precedence over models defined in {Synchronizable#models}.
61
+ # If this parameter is not specified and {Synchronizable#models} is empty,
62
+ # than it will try to sync only those models which have a corresponding synchronizers.
63
+ #
64
+ # @return [Array<[Synchronizable::Context]>] array of synchronization contexts
65
+ #
66
+ # @see Synchronizable::Context
67
+ def self.sync(*models)
68
+ source = source_models(models)
69
+ source.map(&:sync)
70
+ end
71
+
72
+ private
73
+
74
+ def self.source_models(models)
75
+ source = models.present? ? models : default_models
76
+ source = source.present? ? source : find_models
77
+ end
78
+
79
+ def self.default_models
80
+ models.map(&:safe_constantize).compact
81
+ end
82
+
83
+ def self.find_models
84
+ ActiveRecord::Base.descendants.select do |model|
85
+ model.included_modules.include?(Synchronizable::Model)
86
+ end
87
+ end
88
+ end
89
+
90
+ ActiveSupport.on_load(:active_record) do
91
+ include Synchronizable::Model
92
+ end
@@ -0,0 +1,25 @@
1
+ module Synchronizable
2
+ # Synchronization context.
3
+ class Context
4
+ attr_accessor :model, :errors,
5
+ :before, :after, :deleted
6
+
7
+ def initialize(model, parent)
8
+ @model, @parent = model, parent
9
+ @errors = []
10
+ @before, @after, @deleted = 0, 0, 0
11
+ end
12
+
13
+ # @return [String] summary synchronization info.
14
+ def summary_message
15
+ I18n.t('messages.result',
16
+ :model => model,
17
+ :parent => @parent.try(:model),
18
+ :before => before,
19
+ :after => after,
20
+ :deleted => deleted,
21
+ :errors => errors.count
22
+ )
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,57 @@
1
+ require 'synchronizable/dsl/associations/has_one'
2
+ require 'synchronizable/dsl/associations/has_many'
3
+
4
+ module Synchronizable
5
+ module DSL
6
+ module Associations
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :associations
11
+ self.associations = {}
12
+ end
13
+
14
+ module ClassMethods
15
+ [HasOne, HasMany].each do |klass|
16
+ macro = klass.to_s.demodulize.underscore.to_sym
17
+ define_method(macro) do |name, options = {}|
18
+ klass.create(self, name, options)
19
+ end
20
+ end
21
+
22
+ # Builds hash with association as key and array of ids as value.
23
+ #
24
+ # @param attrs [Hash] local record attributes
25
+ #
26
+ # @return [Hash<Synchronizable::Association, Array>] associations hash
27
+ #
28
+ # @raise [MissedAssocationsError] raised when the given
29
+ # attributes hash doesn't required associations
30
+ def associations_for(attrs)
31
+ ensure_required_associations(attrs)
32
+ intersection = self.associations.map { |key, _| key } & attrs.keys
33
+
34
+ Hash[intersection.map { |key|
35
+ [self.associations[key], [*attrs[key].dup]]
36
+ }]
37
+ end
38
+
39
+ private
40
+
41
+ def ensure_required_associations(attrs)
42
+ missing = required_associations - attrs.keys
43
+ if missing.present?
44
+ raise MissedAssociationsError, I18n.t(
45
+ 'errors.missed_associations',
46
+ keys: missing, attrs: attrs
47
+ )
48
+ end
49
+ end
50
+
51
+ def required_associations
52
+ self.associations.select { |_, a| a.required }.map(&:key)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end