harmonize 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'rails', '3.0.9'
4
+ gem 'hashie'
5
+
6
+ group :development, :test, :console do
7
+ gem 'sqlite3'
8
+ gem 'rspec-rails', '~> 2'
9
+ gem 'ruby-debug19', :require => 'ruby-debug'
10
+ gem 'capybara', '~> 1'
11
+ gem 'database_cleaner'
12
+ gem 'factory_girl_rails'
13
+ end
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2011 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # harmonize
2
+
3
+ hahr-muh-nahyz -
4
+
5
+ 1. to bring into harmony, accord, or agreement: to harmonize one's views with the new situation.
6
+ 2. Music . to accompany with appropriate harmony.
7
+ 3. to be in agreement in action, sense, or feeling: Though of different political parties, all the delegates harmonized on civil rights.
8
+ 4. to sing in harmony.
9
+
10
+ harmonize is a rails 3 engine that allows one to harmonize entire scopes of an ActiveRecord model with arbitrary external data sources.
11
+
12
+ harmonize works by allowing one to setup feeds from external data called "sources". harmonize applies the "source" feeds to sets of ActiveRecord instances called "targets". These sets of "source" and "target" data are then handed of to a "strategy" to determine how to use the source to modify the target. When applying a "strategy" harmonize creates a Harmonize::Log which has_many Harmonize::Modifications. These records are then used to report information about the harmonize process.
13
+
14
+ ## Simple Example
15
+
16
+ Lets pretend we work for a company that has a large list of stores. This list of stores in maintained in a database we don't have access to directly. The database administrator wrote a script to export the data in CSV format and upload it to our application. In our application we have a Store model but we always want it to be in harmony with the latest CSV file uploaded by the administrator. Every store has a unique name and an address, and the upstream database uses the name as the primary_key.
17
+
18
+ class Store < ActiveRecord::Base
19
+ validates :name, :address, :presence => true
20
+
21
+ # Setup a harmonizer
22
+ harmonize do |config|
23
+ config.key = :name
24
+ end
25
+
26
+ # By default harmonize expects a class method to be implemented
27
+ # that knows how to provide the source data feed
28
+ def self.harmonize_source_default
29
+ # parse the csv file and return
30
+ # an collection of hash like objects
31
+ end
32
+ end
33
+
34
+ With our Store model wired up as above, we will get a new class method on our model called "harmonize_default!". When we call harmonize_default! harmonize will use the default strategy to harmonize the source records with the target records. In order to understand what actions are taken to bring the targets into harmony, we need to understand Harmonize::Strategies, but that is getting us ahead of ourselves. First lets look at how we configure harmonize.
35
+
36
+ ## Harmonize::Configuration
37
+
38
+ Each call to harmonize creates a Harmonize::Configuration instance that defines how to configure a harmonizer. Here is an example using all the currently available configuration options using non-default settings:
39
+
40
+ class Store < ActiveRecord::Base
41
+ harmonize do |config|
42
+ config.harmonizer_name = :custom_name
43
+ config.key = :the_key
44
+ config.source = lambda do
45
+ get_latest_source_data_feed!
46
+ end
47
+ config.target = lambda do
48
+ where(:active => true)
49
+ end
50
+ config.strategy = YourCustomStrategy
51
+ config.strategy_arguments = { :options => 'needed', :to => 'initialize', :your => 'custom strategy' }
52
+ end
53
+ end
54
+
55
+ ### Harmonizer::Configuration.harmonizer_name
56
+
57
+ harmonize uses the configured harmonize_name as the name of the harmonizer being configured. Each harmonize_name may only be used once. This allows the harmonize method to be called more than once per model.
58
+
59
+ The default harmonizer_name is :default
60
+ This option is used to name special methods used by harmonize. The harmonization method is named with the following convention: "harmonize\_#{harmonizer\_name}!".
61
+
62
+ ### Harmonizer::Configuration.key
63
+
64
+ harmonize uses the configured key to determine what attribute to use to find existing target records.
65
+
66
+ The default key is :id
67
+
68
+ ### Harmonizer::Configuration.source
69
+
70
+ harmonize uses the configured source to gather the latest set of source records. This can be set to a lambda or any other callable object. The only requirement is that it returns a collection of hash like objects.
71
+
72
+ The default source is a lambda
73
+ This lambda calls the default source method who's name is determined by the harmonizer_name. The convention is: "harmonizer\_source\_#{harmonizer_name}".
74
+
75
+ ### Harmonizer::Configuration.target
76
+
77
+ harmonize uses the configured target to gather the latest set of target records. This can be set to a lambda or any other callable object. The only requirement is that it returns an ActiveRecord::Relation
78
+
79
+ The default target is a lambda. This lambda returns an ActiveRecord::Relation via the ActiveRecord::Base#scoped method. Hint...all (named) scopes return ActiveRecord::Relation instances.
80
+
81
+ ### Harmonizer::Configuration.strategy
82
+
83
+ harmonize uses the configured strategy to determine which Harmonize::Strategies::Strategy subclass to use when harmonizing. harmonize uses this setting as well as the strategy_arguments setting to create an instance of the Strategy subclass.
84
+
85
+ The default strategy is Harmonize::Strategies::BasicCrudStrategy
86
+
87
+ ### Harmonizer::Configuration.target
88
+
89
+ harmonize uses the configured strategy_arguments to determine which arguments to use when initializing the set Harmonize::Strategies::Strategy subclass.
90
+
91
+ The default strategy is {}.
92
+
93
+ ## Harmonize::Strategies
94
+
95
+ harmonize figures out how to use your source data to modify your target by using a Harmonize::Strategies::Strategy subclass. A strategy is responsible for determining when and how to create, update, and destroy your target records based on the source records.
96
+
97
+ The default harmonize strategy is Harmonize::Strategies::BasicCrudStrategy. This strategy assumes your source data to be a "source of truth" which reflects exactly how the entire target record set should look. Here are the steps it takes to achieve harmony:
98
+
99
+ 1. For each record in the source, use the configured "key" to find out if a record with that key already exists.
100
+ 2. If an existing record was found in target, update it with any attributes provided by source
101
+ 3. If no existing record was found in target, create a new record with any attributes provided by source
102
+ 4. Save the record and create a Harmonize::Modification record making if we created or updated
103
+ 5. If save has errors, update the Harmonize::Modification record to reflect that and store the error messages
104
+ 6. Save the key of the modified (or errored) record in a collection
105
+ 7. After all source records have been harmonized, destory any target record with a key not in the modified keys collection
106
+ 8. Save Harmonize::Log for reporting the actions took in this harmonization
107
+
108
+ Currently this is the only strategy provided by harmonize, but more will be added when I need them or you send them to me as a pull request.
109
+
110
+ ## Installation
111
+
112
+ Add harmonize to the gem file for your rails application:
113
+
114
+ gem 'harmonize'
115
+
116
+ Run the migration generator in your rails application root:
117
+
118
+ rails generate harmonize:migration
119
+
120
+ Configure your model to use harmonize and implement your source:
121
+
122
+ class MyModel < ActiveRecord::Base
123
+ harmonize
124
+
125
+ def self.harmonize_source_default
126
+ # a collection of hash like objects
127
+ end
128
+ end
129
+
130
+ Use, report bugs, fix them, and send pull requests!
131
+
132
+ ## Plans
133
+
134
+ ## TODO
135
+
136
+ * Maybe move key from Configuration to a strategy_argument as it is not a configuration option really, but a way to change stratgey behaviour.
137
+
138
+ ## Contributors
139
+
140
+ * Bram Swenson <bram@craniumisjar.com>
141
+
142
+ ## Notes
143
+
144
+ Check out the rspec suite for more details about how harmonize works: [BasicCrudStrategy Spec](http://github.com/bramswenson/harmonize/blob/master/spec/lib/harmonize/strategies/basic_crud_strategy_spec.rb)
145
+
146
+ Please let me know how you use harmonize!
147
+
148
+ Thanks in advance!
149
+
150
+ ## License
151
+
152
+ This project rocks and uses MIT-LICENSE.
153
+
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ # encoding: UTF-8
2
+ require 'rubygems'
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rake'
10
+ require 'rspec/core'
11
+ require 'rspec/core/rake_task'
12
+ require 'bundler/gem_tasks'
13
+
14
+ namespace :test do
15
+ RSpec::Core::RakeTask.new(:spec)
16
+ desc 'Setup the test database'
17
+ task :dbsetup do
18
+ results = %x(
19
+ cd spec/dummy &&
20
+ rm db/*.sqlite3 &&
21
+ RAILS_ENV=test rake db:migrate
22
+ )
23
+ puts "dbsetup: #{results}" unless results == ''
24
+ end
25
+
26
+ desc 'Setup the test database and run rspec'
27
+ task :run => %w( test:dbsetup test:spec )
28
+ end
29
+
30
+ task :default => 'test:run'
31
+
@@ -0,0 +1,16 @@
1
+ module Harmonize
2
+ class Log < ActiveRecord::Base
3
+ set_table_name :harmonize_logs
4
+
5
+ validates :key, :class_name, :harmonizer_name, :strategy, :strategy_arguments, :presence => true
6
+
7
+ ModificationOptions = { :class_name => "Harmonize::Modification", :foreign_key => :harmonize_log_id }
8
+
9
+ has_many :modifications, ModificationOptions.merge(:inverse_of => :log, :dependent => :destroy)
10
+ has_many :created, ModificationOptions.merge(:conditions => { :modification_type => 'create' })
11
+ has_many :updated, ModificationOptions.merge(:conditions => { :modification_type => 'update' })
12
+ has_many :destroyed, ModificationOptions.merge(:conditions => { :modification_type => 'destroy'})
13
+ has_many :errored, ModificationOptions.merge(:conditions => { :modification_type => 'error' })
14
+
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module Harmonize
2
+ class Modification < ActiveRecord::Base
3
+ set_table_name :harmonize_modifications
4
+
5
+ validates :harmonize_log_id, :modification_type, :presence => true
6
+ validates :modification_type, :inclusion => %w( create update destroy error )
7
+
8
+ serialize :instance_errors
9
+
10
+ belongs_to :log, :class_name => "Harmonize::Log",
11
+ :foreign_key => :harmonize_log_id, :inverse_of => :modifications
12
+
13
+ scope :created, where(:modification_type => 'create')
14
+ scope :updated, where(:modification_type => 'update')
15
+ scope :destroyed, where(:modification_type => 'destroy')
16
+ scope :errored, where(:modification_type => 'error')
17
+ end
18
+ end
@@ -0,0 +1,95 @@
1
+ module Harmonize
2
+
3
+ module Base
4
+
5
+ def self.included(base)
6
+ base.send :extend, ClassMethods
7
+ base.send :include, InstanceMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ # Enable harmonization on a set of records
13
+ def harmonize(options = {})
14
+ configuration = Harmonize::Configuration.new(options)
15
+ yield(configuration) if block_given?
16
+ configure_harmonizer(configuration)
17
+ end
18
+
19
+ def harmonizers
20
+ @harmonizers ||= {}
21
+ end
22
+
23
+ def harmonize!(harmonizer_name)
24
+ raise UnknownHarmonizerName.new(harmonizer_name) unless harmonizers.has_key?(harmonizer_name)
25
+ harmonizer = harmonizers[harmonizer_name]
26
+ harmonize_log = create_harmonize_log(harmonizer_name)
27
+ strategy = harmonizer.strategy.new(*harmonizer.strategy_arguments)
28
+ strategy.harmonize(:harmonizer => harmonizer, :harmonize_log => harmonize_log)
29
+ close_harmonize_log(harmonize_log)
30
+ end
31
+
32
+ def remove_harmonizer!(harmonizer_name = :default)
33
+ harmonizers.delete(harmonizer_name)
34
+ end
35
+
36
+ private
37
+
38
+ def close_harmonize_log(harmonize_log)
39
+ harmonize_log.end = DateTime.now
40
+ harmonize_log.save!
41
+ harmonize_log
42
+ end
43
+
44
+ def create_harmonize_log(harmonizer_name)
45
+ harmonizer = harmonizers[harmonizer_name]
46
+ Harmonize::Log.create!(
47
+ :start => DateTime.now,
48
+ :class_name => self.class.name,
49
+ :harmonizer_name => harmonizer_name,
50
+ :key => harmonizer.key,
51
+ :strategy => harmonizer.strategy.inspect,
52
+ :strategy_arguments => harmonizer.strategy_arguments.inspect,
53
+ )
54
+ end
55
+
56
+ def default_harmonizer_options(harmonizer_name)
57
+ Harmonize::Configuration.new({
58
+ :source => lambda{ harmonizer_source_method(harmonizer_name) },
59
+ :target => lambda{ scoped }
60
+ })
61
+ end
62
+
63
+ def harmonizer_source_method(harmonizer_name)
64
+ method_name = "harmonizer_source_#{harmonizer_name}".to_sym
65
+ raise HarmonizerSourceUndefined.new(harmonizer_name) unless respond_to?(method_name)
66
+ send(method_name)
67
+ end
68
+
69
+ def validate_harmonizer_configuration(configuration)
70
+ configuration.reverse_merge(default_harmonizer_options(configuration.harmonizer_name))
71
+ end
72
+
73
+ def setup_harmonizer_method(harmonizer_name)
74
+ self.class.instance_eval do
75
+ define_method "harmonize_#{harmonizer_name}!" do
76
+ send(:harmonize!, harmonizer_name)
77
+ end
78
+ end
79
+ end
80
+
81
+ def configure_harmonizer(configuration)
82
+ configuration = validate_harmonizer_configuration(configuration)
83
+ raise DuplicateHarmonizerName.new(configuration.harmonizer_name.to_s) if harmonizers.has_key?(configuration.harmonizer_name)
84
+ harmonizers[configuration.harmonizer_name] = configuration
85
+ setup_harmonizer_method(configuration.harmonizer_name)
86
+ end
87
+
88
+ end
89
+
90
+ module InstanceMethods
91
+ end
92
+
93
+ end
94
+ end
95
+
@@ -0,0 +1,10 @@
1
+ module Harmonize
2
+ class Configuration < Hashie::Dash
3
+ property :source
4
+ property :target
5
+ property :key, :default => :id
6
+ property :harmonizer_name, :default => :default
7
+ property :strategy, :default => Harmonize::Strategies::BasicCrudStrategy
8
+ property :strategy_arguments, :default => Hash.new
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ module Harmonize
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module Harmonize
2
+ class HarmonizeError < StandardError ; end
3
+ class DuplicateHarmonizerName < HarmonizeError ; end
4
+ class UnknownHarmonizerName < HarmonizeError ; end
5
+ class HarmonizerSourceUndefined < HarmonizeError ; end
6
+ class HarmonizerTargetUndefined < HarmonizeError ; end
7
+ end
@@ -0,0 +1,15 @@
1
+ module Harmonize
2
+ module Gemdata
3
+ Name = 'harmonize'
4
+ Version = '0.0.1'
5
+ Authors = [ 'Bram Swenson' ]
6
+ Email = [ 'bram@craniumisajar.com' ]
7
+ Summary = <<-END_SUMMARY
8
+ Bring entire sets of models into harmony with external sources.
9
+ END_SUMMARY
10
+ Description = <<-END_DESCRIPTION
11
+ Bring entire sets of models into harmony with external sources.
12
+ END_DESCRIPTION
13
+ Homepage = 'http://github.com/bramswenson/harmonize'
14
+ end
15
+ end
@@ -0,0 +1,69 @@
1
+ module Harmonize
2
+ module Strategies
3
+
4
+ class BasicCrudStrategy < Strategy
5
+
6
+ def find_target_instance(key, value)
7
+ target_relation = targets.where(key => value)
8
+ instance = target_relation.first rescue nil
9
+ instance ||= target_relation.build
10
+ instance
11
+ end
12
+
13
+ def create_modification(instance)
14
+ unless instance.new_record?
15
+ harmonize_log.modifications.create!(:modification_type => 'update', :before_time => DateTime.now)
16
+ else
17
+ harmonize_log.modifications.create!(:modification_type => 'create', :before_time => DateTime.now)
18
+ end
19
+ end
20
+
21
+ def harmonize_target_instance!(target_instance, source, modification)
22
+ # TODO: this is a mess
23
+ error_message = nil
24
+ errored = false
25
+ begin
26
+ unless target_instance.update_attributes(source)
27
+ errored = true
28
+ error_message = target_instance.errors.full_messages
29
+ end
30
+ rescue ActiveRecord::ActiveRecordError, ActiveRecord::UnknownAttributeError => e
31
+ errored = true
32
+ error_message = e.message
33
+ end
34
+ unless errored
35
+ modification.update_attributes!(:after_time => DateTime.now, :instance_id => target_instance.id)
36
+ else
37
+ modification.update_attributes!(:modification_type => 'error', :instance_errors => error_message)
38
+ end
39
+ end
40
+
41
+ def create_and_update_targets
42
+ sources.each.inject([]) do |keys, source|
43
+ target_instance = find_target_instance(harmonizer.key, source[harmonizer.key])
44
+ modification = create_modification(target_instance)
45
+ harmonize_target_instance!(target_instance, source, modification)
46
+ keys << source[harmonizer.key]
47
+ end
48
+ end
49
+
50
+ def destroy_targets_not_found_in_source(touched_keys)
51
+ # if we didn't get any touched_keys, destroy everything in targets scope
52
+ destroy_scope = touched_keys.empty? ? targets : targets.where("#{harmonizer.key} NOT IN (?)", touched_keys)
53
+ destroy_scope.find_each do |instance|
54
+ modification = harmonize_log.modifications.build(
55
+ :modification_type => 'destroy', :before_time => DateTime.now,
56
+ :instance_id => instance.id
57
+ )
58
+ instance.destroy
59
+ modification.update_attributes!(:after_time => DateTime.now)
60
+ end
61
+ end
62
+
63
+ def harmonize!
64
+ touched_keys = create_and_update_targets
65
+ destroy_targets_not_found_in_source(touched_keys)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,31 @@
1
+ module Harmonize
2
+ module Strategies
3
+
4
+ class Strategy
5
+ attr_accessor :harmonizer, :harmonize_log, :sources, :targets
6
+
7
+ def initialize(attributes = {})
8
+ update_attributes(attributes)
9
+ end
10
+
11
+ def harmonize(attributes)
12
+ update_attributes(attributes)
13
+ self.sources = harmonizer.source.call
14
+ self.targets = harmonizer.target.call
15
+ harmonize!
16
+ end
17
+
18
+ def harmonize!
19
+ end
20
+
21
+ private
22
+
23
+ def update_attributes(attributes)
24
+ attributes.each_pair do |k, v|
25
+ self.send("#{k}=", v)
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,6 @@
1
+ module Harmonize
2
+ module Strategies
3
+ autoload :Strategy, 'harmonize/strategies/strategy'
4
+ autoload :BasicCrudStrategy, 'harmonize/strategies/basic_crud_strategy'
5
+ end
6
+ end
data/lib/harmonize.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'hashie/dash'
2
+ require 'harmonize/errors'
3
+ module Harmonize
4
+ require 'harmonize/engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
5
+ autoload :Base, 'harmonize/base'
6
+ autoload :Strategies, 'harmonize/strategies'
7
+ autoload :Configuration, 'harmonize/configuration'
8
+ end
9
+ ActiveRecord::Base.send :include, Harmonize::Base if defined?(ActiveRecord::Base)
@@ -0,0 +1,29 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module Harmonize
5
+
6
+ class MigrationGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+
9
+ def self.source_root
10
+ File.join(File.dirname(__FILE__), 'templates')
11
+ end
12
+
13
+ def self.next_migration_number(dirname) #:nodoc:
14
+ if ActiveRecord::Base.timestamped_migrations
15
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
16
+ else
17
+ "%.3d" % (current_migration_number(dirname) + 1)
18
+ end
19
+ end
20
+
21
+ def create_migration_file
22
+ f = File.join(File.dirname(__FILE__), 'templates', 'migration.rb')
23
+ migration_template f, 'db/migrate/create_harmonize_tables.rb'
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
@@ -0,0 +1,34 @@
1
+ class CreateHarmonizeTables < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :harmonize_logs do |t|
4
+ t.string :key
5
+ t.string :class_name
6
+ t.string :harmonizer_name
7
+ t.string :strategy
8
+ t.string :strategy_arguments
9
+ t.datetime :start
10
+ t.datetime :end
11
+ t.timestamps
12
+ end
13
+ add_index :harmonize_logs, [ :class_name, :harmonizer_name ]
14
+
15
+ create_table :harmonize_modifications do |t|
16
+ t.integer :harmonize_log_id
17
+ t.integer :instance_id
18
+ t.string :modification_type
19
+ t.datetime :before_time
20
+ t.datetime :after_time
21
+ t.text :instance_errors
22
+ t.timestamps
23
+ end
24
+ add_index :harmonize_modifications, :harmonize_log_id
25
+ add_index :harmonize_modifications, [ :harmonize_log_id, :instance_id ], :name => 'index_harmonize_modification_with_id'
26
+ add_index :harmonize_modifications, [ :harmonize_log_id, :instance_id, :modification_type ], :name => 'index_harmonize_modification_with_id_and_type'
27
+ end
28
+
29
+ def self.down
30
+ drop_table :harmonize_logs
31
+ drop_table :harmonize_modifications
32
+ end
33
+ end
34
+
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: harmonize
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Bram Swenson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-06-26 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: hashie
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: *id001
27
+ description: |
28
+ Bring entire sets of models into harmony with external sources.
29
+
30
+ email:
31
+ - bram@craniumisajar.com
32
+ executables: []
33
+
34
+ extensions: []
35
+
36
+ extra_rdoc_files: []
37
+
38
+ files:
39
+ - app/models/harmonize/log.rb
40
+ - app/models/harmonize/modification.rb
41
+ - lib/harmonize/base.rb
42
+ - lib/harmonize/configuration.rb
43
+ - lib/harmonize/engine.rb
44
+ - lib/harmonize/errors.rb
45
+ - lib/harmonize/gemdata.rb
46
+ - lib/harmonize/strategies/basic_crud_strategy.rb
47
+ - lib/harmonize/strategies/strategy.rb
48
+ - lib/harmonize/strategies.rb
49
+ - lib/harmonize.rb
50
+ - lib/rails/generators/harmonize/migration_generator.rb
51
+ - lib/rails/generators/harmonize/templates/migration.rb
52
+ - MIT-LICENSE
53
+ - Rakefile
54
+ - Gemfile
55
+ - README.md
56
+ has_rdoc: true
57
+ homepage: http://github.com/bramswenson/harmonize
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options: []
62
+
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 2398958541788060618
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.6.1
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Bring entire sets of models into harmony with external sources.
87
+ test_files: []
88
+