draisine 0.7.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +70 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +134 -0
  9. data/Rakefile +6 -0
  10. data/app/controllers/draisine/soap_controller.rb +49 -0
  11. data/bin/console +7 -0
  12. data/bin/setup +6 -0
  13. data/config/routes.rb +4 -0
  14. data/draisine.gemspec +32 -0
  15. data/lib/draisine/active_record.rb +191 -0
  16. data/lib/draisine/auditor/result.rb +48 -0
  17. data/lib/draisine/auditor.rb +130 -0
  18. data/lib/draisine/concerns/array_setter.rb +23 -0
  19. data/lib/draisine/concerns/attributes_mapping.rb +46 -0
  20. data/lib/draisine/concerns/import.rb +36 -0
  21. data/lib/draisine/conflict_detector.rb +38 -0
  22. data/lib/draisine/conflict_resolver.rb +97 -0
  23. data/lib/draisine/engine.rb +6 -0
  24. data/lib/draisine/importer.rb +111 -0
  25. data/lib/draisine/ip_checker.rb +15 -0
  26. data/lib/draisine/jobs/inbound_delete_job.rb +8 -0
  27. data/lib/draisine/jobs/inbound_update_job.rb +8 -0
  28. data/lib/draisine/jobs/job_base.rb +39 -0
  29. data/lib/draisine/jobs/outbound_create_job.rb +7 -0
  30. data/lib/draisine/jobs/outbound_delete_job.rb +7 -0
  31. data/lib/draisine/jobs/outbound_update_job.rb +7 -0
  32. data/lib/draisine/jobs/soap_delete_job.rb +7 -0
  33. data/lib/draisine/jobs/soap_update_job.rb +7 -0
  34. data/lib/draisine/partitioner.rb +73 -0
  35. data/lib/draisine/poller.rb +101 -0
  36. data/lib/draisine/query_mechanisms/base.rb +15 -0
  37. data/lib/draisine/query_mechanisms/default.rb +13 -0
  38. data/lib/draisine/query_mechanisms/last_modified_date.rb +18 -0
  39. data/lib/draisine/query_mechanisms/system_modstamp.rb +18 -0
  40. data/lib/draisine/query_mechanisms.rb +18 -0
  41. data/lib/draisine/registry.rb +22 -0
  42. data/lib/draisine/setup.rb +97 -0
  43. data/lib/draisine/soap_handler.rb +79 -0
  44. data/lib/draisine/syncer.rb +52 -0
  45. data/lib/draisine/type_mapper.rb +105 -0
  46. data/lib/draisine/util/caching_client.rb +73 -0
  47. data/lib/draisine/util/hash_diff.rb +39 -0
  48. data/lib/draisine/util/parse_time.rb +14 -0
  49. data/lib/draisine/util/salesforce_comparisons.rb +53 -0
  50. data/lib/draisine/version.rb +3 -0
  51. data/lib/draisine.rb +48 -0
  52. data/lib/ext/databasedotcom.rb +98 -0
  53. data/lib/generators/draisine/delta_migration_generator.rb +77 -0
  54. data/lib/generators/draisine/integration_generator.rb +53 -0
  55. data/lib/generators/draisine/templates/delta_migration.rb +24 -0
  56. data/lib/generators/draisine/templates/migration.rb +21 -0
  57. data/lib/generators/draisine/templates/model.rb +11 -0
  58. data/salesforce/sample_delete_trigger.apex +7 -0
  59. data/salesforce/sample_test_class_for_delete_trigger.apex +15 -0
  60. metadata +242 -0
@@ -0,0 +1,53 @@
1
+ module Draisine
2
+ module SalesforceComparisons
3
+ JSON_TIME_REGEX = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(.\d{3})?[+\-]\d{2}:\d{2}\z/
4
+ EPSILON = 1e-10
5
+ module_function
6
+
7
+ def salesforce_equals?(value, other)
8
+ value = salesforce_cleanup(value)
9
+ other = salesforce_cleanup(other)
10
+ value = salesforce_coerce(value)
11
+ other = salesforce_coerce(other)
12
+
13
+ return fp_equals?(value, other) if numeric?(value) && numeric?(other)
14
+ return value == other if value.class != other.class
15
+
16
+ case value
17
+ when String
18
+ normalize_string(value) == normalize_string(other)
19
+ else
20
+ value == other
21
+ end
22
+ end
23
+
24
+ def salesforce_cleanup(value)
25
+ case value
26
+ when String
27
+ value.encode("UTF-8", invalid: :replace, undef: :replace, replace: "?")
28
+ else
29
+ value
30
+ end
31
+ end
32
+
33
+ def salesforce_coerce(value)
34
+ if value.kind_of?(DateTime) || value.kind_of?(Time) || value.kind_of?(Date) ||
35
+ value =~ JSON_TIME_REGEX
36
+ value = value.to_time.utc.change(usec: 0)
37
+ end
38
+ value
39
+ end
40
+
41
+ def normalize_string(string)
42
+ string.gsub(/\W/, "")
43
+ end
44
+
45
+ def fp_equals?(value, other)
46
+ (value - other).abs <= EPSILON
47
+ end
48
+
49
+ def numeric?(value)
50
+ value.kind_of?(Numeric)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Draisine
2
+ VERSION = "0.7.10"
3
+ end
data/lib/draisine.rb ADDED
@@ -0,0 +1,48 @@
1
+ require "active_support"
2
+ require "active_support/core_ext"
3
+ require "active_support/concern"
4
+ require "active_job/base"
5
+
6
+ require "databasedotcom"
7
+ require "ext/databasedotcom"
8
+
9
+ require "draisine/version"
10
+ require "draisine/setup"
11
+
12
+ require "draisine/jobs/job_base"
13
+ require "draisine/jobs/inbound_update_job"
14
+ require "draisine/jobs/inbound_delete_job"
15
+ require "draisine/jobs/soap_update_job"
16
+ require "draisine/jobs/soap_delete_job"
17
+ require "draisine/jobs/outbound_create_job"
18
+ require "draisine/jobs/outbound_update_job"
19
+ require "draisine/jobs/outbound_delete_job"
20
+
21
+ require "draisine/registry"
22
+ require "draisine/ip_checker"
23
+
24
+ require "draisine/util/hash_diff"
25
+ require "draisine/util/parse_time"
26
+ require "draisine/util/salesforce_comparisons"
27
+ require "draisine/util/caching_client"
28
+
29
+ require "draisine/query_mechanisms"
30
+ require "draisine/partitioner"
31
+ require "draisine/auditor"
32
+ require "draisine/conflict_detector"
33
+ require "draisine/conflict_resolver"
34
+ require "draisine/syncer"
35
+ require "draisine/type_mapper"
36
+ require "draisine/soap_handler"
37
+ require "draisine/engine"
38
+ require "draisine/importer"
39
+ require "draisine/poller"
40
+
41
+ require "draisine/concerns/array_setter"
42
+ require "draisine/concerns/attributes_mapping"
43
+ require "draisine/concerns/import"
44
+ require "draisine/active_record"
45
+
46
+
47
+ module Draisine
48
+ end
@@ -0,0 +1,98 @@
1
+ module Databasedotcom
2
+ class Client
3
+ # Fetches a collection of sobjects with given ids
4
+ # Useful in conjunction with get_updated / get_deleted calls
5
+ def fetch_multiple(classname, ids, batch_size = 100, field_list = nil)
6
+ return [] unless ids.present?
7
+ klass = find_or_materialize(classname)
8
+ field_list ||= klass.field_list.split(",")
9
+ field_list = field_list | ["Id"]
10
+ field_list = field_list.join(",")
11
+ ids.in_groups_of(batch_size).flat_map do |ids|
12
+ query <<-EOQ
13
+ SELECT #{field_list}
14
+ FROM #{klass.sobject_name}
15
+ WHERE id IN (#{ids.map {|id| "'%s'" % id}.join(',')})
16
+ EOQ
17
+ end
18
+ end
19
+
20
+ # Returns a list of updated sobject ids for provided date range
21
+ # reference: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_getupdated.htm?search_text=getUpdated
22
+ def get_updated(classname, start_date, end_date = Time.current)
23
+ result = http_get("/services/data/v#{self.version}/sobjects/#{classname}/updated",
24
+ start: prepare_date_arg(start_date),
25
+ end: prepare_date_arg(end_date))
26
+ JSON.parse(result.body)
27
+ rescue Databasedotcom::SalesForceError => e
28
+ if e.message.include?("is not replicable")
29
+ {}
30
+ else
31
+ raise
32
+ end
33
+ end
34
+
35
+ def get_updated_ids(classname, start_date, end_date = Time.current)
36
+ get_updated(classname, start_date, end_date).fetch("ids", [])
37
+ end
38
+
39
+ # Returns a list of updated sobject ids for provided date range
40
+ # reference: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_getdeleted.htm?search_text=getUpdated
41
+ def get_deleted(classname, start_date, end_date = Time.current)
42
+ result = http_get("/services/data/v#{self.version}/sobjects/#{classname}/deleted",
43
+ start: prepare_date_arg(start_date),
44
+ end: prepare_date_arg(end_date))
45
+ JSON.parse(result.body)
46
+ rescue Databasedotcom::SalesForceError => e
47
+ if e.message.include?("is not replicable")
48
+ {}
49
+ else
50
+ raise
51
+ end
52
+ end
53
+
54
+ def get_deleted_ids(classname, start_date, end_date = Time.current)
55
+ get_deleted(classname, start_date, end_date).fetch("deletedRecords", []).map {|r| r["id"] }
56
+ end
57
+
58
+ def count(classname)
59
+ query("SELECT COUNT() FROM #{classname}").total_size
60
+ end
61
+
62
+ protected
63
+
64
+ def prepare_date_arg(date)
65
+ return date.to_time.iso8601 if date.respond_to?(:to_time)
66
+ date
67
+ end
68
+
69
+ def find_or_materialize(class_or_classname)
70
+ if class_or_classname.is_a?(Class)
71
+ clazz = class_or_classname
72
+ else
73
+ match = class_or_classname.match(/(?:(.+)::)?(\w+)$/)
74
+ preceding_namespace = match[1]
75
+ classname = match[2]
76
+ raise ArgumentError if preceding_namespace && preceding_namespace != module_namespace.name
77
+ clazz = module_namespace.const_get(classname.to_sym, false) rescue nil # fix inherited namespace
78
+ clazz ||= self.materialize(classname)
79
+ end
80
+ clazz
81
+ end
82
+ end
83
+
84
+ module Sobject
85
+ module NumericFieldsExtensions
86
+ def register_field(name, field)
87
+ super
88
+ %w[scale precision length byteLength].each do |attr|
89
+ self.type_map[name][attr.underscore.to_sym] = field[attr] if field[attr]
90
+ end
91
+ end
92
+ end
93
+
94
+ class <<Sobject
95
+ prepend NumericFieldsExtensions
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,77 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module Draisine
4
+ class DeltaMigrationGenerator < Rails::Generators::Base
5
+ self.source_root(File.expand_path('../templates/', __FILE__))
6
+
7
+ desc <<-DESC
8
+ Generates a migration for missing columns for given model
9
+ DESC
10
+
11
+ argument :model,
12
+ type: :string,
13
+ banner: "ModelName",
14
+ desc: "Model name for active record (e.g. Lead)",
15
+ required: true
16
+
17
+ argument :salesforce_object_name,
18
+ type: :string,
19
+ banner: "SalesforceObjectName",
20
+ desc: "Salesforce object name (inferred from ModelName by default)",
21
+ required: false
22
+
23
+ class_option :fields,
24
+ type: :array,
25
+ banner: "Field1,Field2",
26
+ desc: "Fields to generate (would otherwise generate all missing fields)",
27
+ required: false
28
+
29
+ def generate
30
+ @model_name = model.classify.singularize
31
+ @model_file = "app/models/#{model.underscore.singularize}.rb"
32
+ @table_name = model.underscore.gsub("/", "_").pluralize
33
+ @migration_title = "DeltaUpdate#{model.classify.gsub('::', '').pluralize}#{migration_uid}"
34
+ @migration_file = existing_migration_name(@migration_title) ||
35
+ "db/migrate/#{migration_number}_#{@migration_title.underscore}.rb"
36
+ @fields = (options["fields"] || []).map(&:downcase)
37
+ @salesforce_object_name = salesforce_object_name || model.classify.demodulize
38
+ @materialized_model = Draisine.salesforce_client.materialize(@salesforce_object_name)
39
+ @mapper = Draisine::TypeMapper.new(@materialized_model.type_map)
40
+ @existing_columns = ActiveRecord::Base.connection
41
+ .columns(@table_name)
42
+ .each_with_object({}) {|col, h| h[col.name] = Draisine::TypeMapper::ActiveRecordColumnDef.from_ar_column(col) }
43
+ @new_ar_col_defs = @mapper.active_record_column_defs
44
+ .reject {|col_def| @existing_columns.key?(col_def.column_name) }
45
+ .select {|col_def| column_in_required_fields?(col_def.column_name) }
46
+ @changed_ar_col_defs = @mapper.active_record_column_defs
47
+ .select {|col_def| @existing_columns.key?(col_def.column_name) }
48
+ .select {|col_def| different_column?(@existing_columns[col_def.column_name], col_def) }
49
+ .select {|col_def| column_in_required_fields?(col_def.column_name) }
50
+ template "delta_migration.rb", @migration_file
51
+ end
52
+
53
+ protected
54
+
55
+ def migration_number
56
+ Time.current.utc.strftime("%Y%m%d%H%M%S")
57
+ end
58
+
59
+ def migration_uid
60
+ Time.current.utc.strftime("%Y%m%d")
61
+ end
62
+
63
+ def column_in_required_fields?(column)
64
+ @fields.empty? || @fields.include?(column.to_s.downcase)
65
+ end
66
+
67
+ # This is needed for rails destroy to work, since every time we generate a new name for our migration
68
+ def existing_migration_name(migration_title)
69
+ Dir.glob("#{Rails.root}/db/migrate/[0-9]*_#{migration_title.underscore}.rb").first
70
+ end
71
+
72
+ def different_column?(first, second)
73
+ first.column_type != second.column_type ||
74
+ (first.options[:limit] && second.options[:limit] && first.options[:limit] != second.options[:limit])
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,53 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module Draisine
4
+ class IntegrationGenerator < Rails::Generators::Base
5
+ self.source_root(File.expand_path('../templates/', __FILE__))
6
+
7
+ desc <<-DESC
8
+ Uses materialized models from salesforce.
9
+ Generates a migration creating an integration table for a salesforce model and
10
+ an active_record model under app/models/salesforce/
11
+ DESC
12
+
13
+ argument :model, type: :string,
14
+ banner: "ModelName",
15
+ desc: "Model name for active record (e.g. Lead)",
16
+ required: true
17
+ argument :salesforce_object_name, type: :string,
18
+ banner: "SalesforceObjectName",
19
+ desc: "Salesforce object name (inferred from ModelName by default)",
20
+ required: false
21
+ def create_salesforce_integrations
22
+ @model_name = model.classify.singularize
23
+ @model_file = "app/models/#{model.underscore.singularize}.rb"
24
+ @table_name = model.underscore.gsub("/", "_").pluralize
25
+ @migration_title = "CreateSalesforce#{model.classify.gsub('::', '').pluralize}"
26
+ @migration_file = existing_migration_name(@migration_title) ||
27
+ "db/migrate/#{migration_number}_#{@migration_title.underscore}.rb"
28
+
29
+ @salesforce_object_name = salesforce_object_name || model.classify.demodulize
30
+ @materialized_model = Draisine.salesforce_client.materialize(@salesforce_object_name)
31
+ @mapper = Draisine::TypeMapper.new(@materialized_model.type_map)
32
+ @ar_col_defs = @mapper.active_record_column_defs
33
+ @columns = @mapper.active_record_column_defs.map(&:column_name)
34
+ @serialized_columns = @mapper.serialized_columns
35
+ @array_columns = @mapper.array_columns
36
+ @non_audited_columns = @mapper.columns - @mapper.updateable_columns
37
+
38
+ template "migration.rb", @migration_file
39
+ template "model.rb", @model_file
40
+ end
41
+
42
+ protected
43
+
44
+ def migration_number
45
+ Time.current.utc.strftime("%Y%m%d%H%M%S")
46
+ end
47
+
48
+ # This is needed for rails destroy to work, since every time we generate a new name for our migration
49
+ def existing_migration_name(migration_title)
50
+ Dir.glob("#{Rails.root}/db/migrate/[0-9]*_#{migration_title.underscore}.rb").first
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,24 @@
1
+ class <%= @migration_title %> < ActiveRecord::Migration
2
+ def up
3
+ change_table :<%= @table_name %>, bulk: true do |t|
4
+ <%- @new_ar_col_defs.each do |col_def| -%>
5
+ t.column :<%= col_def.column_name %>, :<%= col_def.column_type %>, <%= col_def.options.inspect %>
6
+ <%- end -%>
7
+ <%- @changed_ar_col_defs.each do |col_def| -%>
8
+ t.change :<%= col_def.column_name %>, :<%= col_def.column_type %>, <%= col_def.options.inspect %>
9
+ <%- end -%>
10
+ end
11
+ end
12
+
13
+ def down
14
+ change_table :<%= @table_name %>, bulk: true do |t|
15
+ <%- @new_ar_col_defs.each do |col_def| -%>
16
+ t.remove :<%= col_def.column_name %>
17
+ <%- end -%>
18
+ <%- @changed_ar_col_defs.each do |col_def| -%>
19
+ <%- ex_col = @existing_columns[col_def.column_name] -%>
20
+ t.change :<%= ex_col.column_name %>, :<%= ex_col.column_type %>, <%= ex_col.options.inspect %>
21
+ <%- end -%>
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ class <%= @migration_title %> < ActiveRecord::Migration
2
+ def up
3
+ create_table :<%= @table_name %> do |t|
4
+ <%- @ar_col_defs.each do |col_def| -%>
5
+ t.column :<%= col_def.column_name %>, :<%= col_def.column_type %>, <%= col_def.options.inspect %>
6
+ <%- end -%>
7
+
8
+ t.timestamps
9
+ t.index :updated_at
10
+
11
+ # These are draisine-specific columns
12
+ t.column :salesforce_id, :string, :limit => 18
13
+ t.column :salesforce_updated_at, :datetime
14
+ t.index :salesforce_id, :unique => true
15
+ end
16
+ end
17
+
18
+ def down
19
+ drop_table :<%= @table_name %>
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ class <%= @model_name %> < ActiveRecord::Base
2
+ self.table_name = <%= @table_name.inspect %>
3
+ <%- @serialized_columns.each do |col| -%>
4
+ serialize :<%= col %>, JSON
5
+ <%- end -%>
6
+
7
+ salesforce_syncable synced_attributes: <%= @columns.map(&:to_sym).inspect %>,
8
+ non_audited_attributes: <%= @non_audited_columns.map(&:to_sym).inspect %>,
9
+ array_attributes: <%= @array_columns.map(&:to_sym).inspect %>,
10
+ salesforce_object_name: <%= @salesforce_object_name.inspect %>
11
+ end
@@ -0,0 +1,7 @@
1
+ trigger Create_Deleted_Object on Lead (after delete) {
2
+ List <Lead> leads = Trigger.old;
3
+ for (Lead lead: leads) {
4
+ Deleted_Object__c delObj = new Deleted_Object__c(Object_Id__c = lead.Id, Object_Type__c = 'Salesforce::Lead');
5
+ insert delObj;
6
+ }
7
+ }
@@ -0,0 +1,15 @@
1
+ @isTest(seeAllData=false)
2
+
3
+ private class CreateDeletedObjectAfterLeadDeleteTest {
4
+ static testMethod void testCreatedDeletedObject() {
5
+ Test.startTest();
6
+ Lead lead = new Lead(FirstName = 'John', LastName = 'Snow');
7
+ insert lead;
8
+ System.assertEquals([SELECT COUNT() FROM Deleted_Object__c], 0);
9
+ String id = lead.Id;
10
+ delete lead;
11
+ System.assertEquals([SELECT COUNT() FROM Deleted_Object__c], 1);
12
+ System.assertEquals([SELECT Object_Id__c FROM Deleted_Object__c LIMIT 1].Object_Id__c, id);
13
+ Test.stopTest();
14
+ }
15
+ }
metadata ADDED
@@ -0,0 +1,242 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: draisine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.10
5
+ platform: ruby
6
+ authors:
7
+ - Mark Abramov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-12-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-collection_matchers
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rails
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '4.2'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '4.2'
139
+ - !ruby/object:Gem::Dependency
140
+ name: databasedotcom
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.3'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.3'
153
+ description: Bidirectional synchronization for salesforce / activerecord
154
+ email:
155
+ - mark.abramov@chloeandisabel.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - ".gitignore"
161
+ - ".rspec"
162
+ - ".travis.yml"
163
+ - Gemfile
164
+ - Guardfile
165
+ - LICENSE.txt
166
+ - README.md
167
+ - Rakefile
168
+ - app/controllers/draisine/soap_controller.rb
169
+ - bin/console
170
+ - bin/setup
171
+ - config/routes.rb
172
+ - draisine.gemspec
173
+ - lib/draisine.rb
174
+ - lib/draisine/active_record.rb
175
+ - lib/draisine/auditor.rb
176
+ - lib/draisine/auditor/result.rb
177
+ - lib/draisine/concerns/array_setter.rb
178
+ - lib/draisine/concerns/attributes_mapping.rb
179
+ - lib/draisine/concerns/import.rb
180
+ - lib/draisine/conflict_detector.rb
181
+ - lib/draisine/conflict_resolver.rb
182
+ - lib/draisine/engine.rb
183
+ - lib/draisine/importer.rb
184
+ - lib/draisine/ip_checker.rb
185
+ - lib/draisine/jobs/inbound_delete_job.rb
186
+ - lib/draisine/jobs/inbound_update_job.rb
187
+ - lib/draisine/jobs/job_base.rb
188
+ - lib/draisine/jobs/outbound_create_job.rb
189
+ - lib/draisine/jobs/outbound_delete_job.rb
190
+ - lib/draisine/jobs/outbound_update_job.rb
191
+ - lib/draisine/jobs/soap_delete_job.rb
192
+ - lib/draisine/jobs/soap_update_job.rb
193
+ - lib/draisine/partitioner.rb
194
+ - lib/draisine/poller.rb
195
+ - lib/draisine/query_mechanisms.rb
196
+ - lib/draisine/query_mechanisms/base.rb
197
+ - lib/draisine/query_mechanisms/default.rb
198
+ - lib/draisine/query_mechanisms/last_modified_date.rb
199
+ - lib/draisine/query_mechanisms/system_modstamp.rb
200
+ - lib/draisine/registry.rb
201
+ - lib/draisine/setup.rb
202
+ - lib/draisine/soap_handler.rb
203
+ - lib/draisine/syncer.rb
204
+ - lib/draisine/type_mapper.rb
205
+ - lib/draisine/util/caching_client.rb
206
+ - lib/draisine/util/hash_diff.rb
207
+ - lib/draisine/util/parse_time.rb
208
+ - lib/draisine/util/salesforce_comparisons.rb
209
+ - lib/draisine/version.rb
210
+ - lib/ext/databasedotcom.rb
211
+ - lib/generators/draisine/delta_migration_generator.rb
212
+ - lib/generators/draisine/integration_generator.rb
213
+ - lib/generators/draisine/templates/delta_migration.rb
214
+ - lib/generators/draisine/templates/migration.rb
215
+ - lib/generators/draisine/templates/model.rb
216
+ - salesforce/sample_delete_trigger.apex
217
+ - salesforce/sample_test_class_for_delete_trigger.apex
218
+ homepage: https://github.com/chloeandisabel/draisine
219
+ licenses:
220
+ - MIT
221
+ metadata: {}
222
+ post_install_message:
223
+ rdoc_options: []
224
+ require_paths:
225
+ - lib
226
+ required_ruby_version: !ruby/object:Gem::Requirement
227
+ requirements:
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ version: '0'
231
+ required_rubygems_version: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: '0'
236
+ requirements: []
237
+ rubyforge_project:
238
+ rubygems_version: 2.5.1
239
+ signing_key:
240
+ specification_version: 4
241
+ summary: Synchronization machinery for salesforce
242
+ test_files: []