Skip to content

Commit

Permalink
[WORKS!] mostly...kinda...i think...need more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Bram Swenson committed Jun 26, 2011
1 parent 30e49a9 commit 8bf12b8
Show file tree
Hide file tree
Showing 25 changed files with 583 additions and 33 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
source "http://rubygems.org"

gem 'rails', '3.0.9'
gem 'hashie'

group :development, :test, :console do
gem 'sqlite3'
Expand Down
8 changes: 7 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ namespace :test do
RSpec::Core::RakeTask.new(:spec)
desc 'Setup the test database'
task :dbsetup do
results = %x( cd spec/dummy && RAILS_ENV=test rake db:migrate )
results = %x(
cd spec/dummy &&
rm db/migrate/*create_harmonize_tables.rb &&
rm db/*.sqlite3 &&
rails g harmonize:migration &&
RAILS_ENV=test rake db:migrate
)
puts "dbsetup: #{results}" unless results == ''
end

Expand Down
11 changes: 11 additions & 0 deletions app/models/harmonize/log.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Harmonize
class Log < ActiveRecord::Base
set_table_name :harmonize_logs

has_many :modifications, :class_name => "Harmonize::Modification",
:foreign_key => :harmonize_log_id, :inverse_of => :log,
:dependent => :destroy

validates :key, :class_name, :harmonizer_name, :strategy, :strategy_arguments, :presence => true
end
end
12 changes: 12 additions & 0 deletions app/models/harmonize/modification.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Harmonize
class Modification < ActiveRecord::Base
set_table_name :harmonize_modifications

belongs_to :log, :class_name => "Harmonize::Log",
:foreign_key => :harmonize_log_id, :inverse_of => :modifications

validates :harmonize_log_id, :modification_type, :presence => true
validates :modification_type, :inclusion => %w( create update destroy error )
serialize :instance_errors
end
end
11 changes: 6 additions & 5 deletions harmonize.gemspec
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
require 'harmonize/gemspec'
$LOAD_PATH.unshift(File.expand_path('lib', File.dirname(__FILE__)))
require 'harmonize/gemdata'

Gem::Specification.new do |s|
s.name = Harmonize::Name
s.version = Harmonize::Version
s.summary = Harmonize::Summary
s.description = Harmonize::Description
s.name = Harmonize::Gemdata::Name
s.version = Harmonize::Gemdata::Version
s.summary = Harmonize::Gemdata::Summary
s.description = Harmonize::Gemdata::Description
s.files = Dir["{app,lib,config}/**/*"] + ["MIT-LICENSE", "Rakefile", "Gemfile", "README.rdoc"]
end
9 changes: 7 additions & 2 deletions lib/harmonize.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
require 'hashie/dash'
require 'harmonize/errors'
module Harmonize
autoload :Base, 'harmonize/base'
require 'harmonize/engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
autoload :Base, 'harmonize/base'
autoload :Strategies, 'harmonize/strategies'
autoload :Configuration, 'harmonize/configuration'
end
ActiveRecord::Base.send :include, Harmonize::Base
ActiveRecord::Base.send :include, Harmonize::Base if defined?(ActiveRecord::Base)
78 changes: 77 additions & 1 deletion lib/harmonize/base.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module Harmonize

module Base

def self.included(base)
Expand All @@ -7,8 +8,83 @@ def self.included(base)
end

module ClassMethods
def harmonize(*args)

# Enable harmonization on a set of records
def harmonize(options = {})
configuration = Harmonize::Configuration.new(options)
yield(configuration) if block_given?
configure_harmonizer(configuration)
end

def harmonizers
@harmonizers ||= {}
end

def harmonize!(harmonizer_name)
raise UnknownHarmonizerName.new(harmonizer_name) unless harmonizers.has_key?(harmonizer_name)
harmonizer = harmonizers[harmonizer_name]
harmonize_log = create_harmonize_log(harmonizer_name)
strategy = harmonizer.strategy.new(*harmonizer.strategy_arguments)
strategy.harmonize(:harmonizer => harmonizer, :harmonize_log => harmonize_log)
close_harmonize_log(harmonize_log)
end

def remove_harmonizer!(harmonizer_name = :default)
harmonizers.delete(harmonizer_name)
end

private

def close_harmonize_log(harmonize_log)
harmonize_log.end = DateTime.now
harmonize_log.save!
harmonize_log
end

def create_harmonize_log(harmonizer_name)
harmonizer = harmonizers[harmonizer_name]
Harmonize::Log.create!(
:start => DateTime.now,
:class_name => self.class.name,
:harmonizer_name => harmonizer_name,
:key => harmonizer.key,
:strategy => harmonizer.strategy.inspect,
:strategy_arguments => harmonizer.strategy_arguments.inspect,
)
end

def default_harmonizer_options(harmonizer_name)
Harmonize::Configuration.new({
:source => lambda{ harmonizer_source_method(harmonizer_name) },
:target => lambda{ find_each }
})
end

def harmonizer_source_method(harmonizer_name)
method_name = "harmonizer_source_#{harmonizer_name}".to_sym
raise HarmonizerSourceUndefined.new(harmonizer_name) unless respond_to?(method_name)
send(method_name)
end

def validate_harmonizer_configuration(configuration)
configuration.reverse_merge(default_harmonizer_options(configuration.harmonizer_name))
end

def setup_harmonizer_method(harmonizer_name)
self.class.instance_eval do
define_method "harmonize_#{harmonizer_name}!" do
send(:harmonize!, harmonizer_name)
end
end
end

def configure_harmonizer(configuration)
configuration = validate_harmonizer_configuration(configuration)
raise DuplicateHarmonizerName.new(configuration.harmonizer_name.to_s) if harmonizers.has_key?(configuration.harmonizer_name)
harmonizers[configuration.harmonizer_name] = configuration
setup_harmonizer_method(configuration.harmonizer_name)
end

end

module InstanceMethods
Expand Down
10 changes: 10 additions & 0 deletions lib/harmonize/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Harmonize
class Configuration < Hashie::Dash
property :source
property :target
property :key, :default => :id
property :harmonizer_name, :default => :default
property :strategy, :default => Harmonize::Strategies::BasicCrudStrategy
property :strategy_arguments, :default => Hash.new
end
end
4 changes: 4 additions & 0 deletions lib/harmonize/engine.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module Harmonize
class Engine < ::Rails::Engine
end
end
7 changes: 7 additions & 0 deletions lib/harmonize/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Harmonize
class HarmonizeError < StandardError ; end
class DuplicateHarmonizerName < HarmonizeError ; end
class UnknownHarmonizerName < HarmonizeError ; end
class HarmonizerSourceUndefined < HarmonizeError ; end
class HarmonizerTargetUndefined < HarmonizeError ; end
end
2 changes: 2 additions & 0 deletions lib/harmonize/gemspec.rb → lib/harmonize/gemdata.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module Harmonize
module Gemdata
Name = 'harmonize'
Version = '0.0.1'
Summary = <<-END_SUMMARY
Expand All @@ -7,4 +8,5 @@ module Harmonize
Description = <<-END_DESCRIPTION
Bring entire sets of models into harmony with external sources.
END_DESCRIPTION
end
end
6 changes: 6 additions & 0 deletions lib/harmonize/strategies.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Harmonize
module Strategies
autoload :Strategy, 'harmonize/strategies/strategy'
autoload :BasicCrudStrategy, 'harmonize/strategies/basic_crud_strategy'
end
end
60 changes: 60 additions & 0 deletions lib/harmonize/strategies/basic_crud_strategy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module Harmonize
module Strategies

class BasicCrudStrategy < Strategy

def find_target_instance(key, value)
target_relation = targets.where(key => value)
instance = target_relation.first rescue nil
instance ||= target_relation.build
instance
end

def do_not_delete_these_keys
@do_not_delete_these_keys ||= []
end

def create_modification(instance)
unless instance.new_record?
harmonize_log.modifications.create!(:modification_type => 'update', :before_time => DateTime.now)
else
harmonize_log.modifications.create!(:modification_type => 'create', :before_time => DateTime.now)
end
end

def harmonize_target_instance!(target_instance, source, modification)
unless target_instance.update_attributes(source)
modification.update_attributes!(:modification_type => 'error',
:instance_errors => target_instance.errors.full_messages)
else
modification.update_attributes!(:after_time => DateTime.now, :instance_id => target_instance.id)
end
end

def create_and_update_targets
sources.each do |source|
target_instance = find_target_instance(harmonizer.key, source[harmonizer.key])
modification = create_modification(target_instance)
harmonize_target_instance!(target_instance, source, modification)
do_not_delete_these_keys << source[harmonizer.key]
end
end

def destroy_targets_not_found_in_source
targets.where(harmonizer.key => !do_not_delete_these_keys).find_each do |instance|
modification = harmonize_log.modifications.build(
:modification_type => 'destroy', :before_time => DateTime.now,
:instance_id => instance.id
)
instance.destroy
modification.update_attributes!(:after_time => DateTime.now)
end
end

def harmonize!
create_and_update_targets
destroy_targets_not_found_in_source
end
end
end
end
31 changes: 31 additions & 0 deletions lib/harmonize/strategies/strategy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module Harmonize
module Strategies

class Strategy
attr_accessor :harmonizer, :harmonize_log, :sources, :targets

def initialize(attributes = {})
update_attributes(attributes)
end

def harmonize(attributes)
update_attributes(attributes)
self.sources = harmonizer.source.call
self.targets = harmonizer.target.call
harmonize!
end

def harmonize!
end

private

def update_attributes(attributes)
attributes.each_pair do |k, v|
self.send("#{k}=", v)
end
end

end
end
end
29 changes: 29 additions & 0 deletions lib/rails/generators/harmonize/migration_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require 'rails/generators'
require 'rails/generators/migration'

module Harmonize

class MigrationGenerator < Rails::Generators::Base
include Rails::Generators::Migration

def self.source_root
File.join(File.dirname(__FILE__), 'templates')
end

def self.next_migration_number(dirname) #:nodoc:
if ActiveRecord::Base.timestamped_migrations
Time.now.utc.strftime("%Y%m%d%H%M%S")
else
"%.3d" % (current_migration_number(dirname) + 1)
end
end

def create_migration_file
f = File.join(File.dirname(__FILE__), 'templates', 'migration.rb')
migration_template f, 'db/migrate/create_harmonize_tables.rb'
end

end

end

34 changes: 34 additions & 0 deletions lib/rails/generators/harmonize/templates/migration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class CreateHarmonizeTables < ActiveRecord::Migration
def self.up
create_table :harmonize_logs do |t|
t.string :key
t.string :class_name
t.string :harmonizer_name
t.string :strategy
t.string :strategy_arguments
t.datetime :start
t.datetime :end
t.timestamps
end
add_index :harmonize_logs, [ :class_name, :harmonizer_name ]

create_table :harmonize_modifications do |t|
t.integer :harmonize_log_id
t.integer :instance_id
t.string :modification_type
t.datetime :before_time
t.datetime :after_time
t.text :instance_errors
t.timestamps
end
add_index :harmonize_modifications, :harmonize_log_id
add_index :harmonize_modifications, [ :harmonize_log_id, :instance_id ], :name => 'index_harmonize_modification_with_id'
add_index :harmonize_modifications, [ :harmonize_log_id, :instance_id, :modification_type ], :name => 'index_harmonize_modification_with_id_and_type'
end

def self.down
drop_table :harmonize_logs
drop_table :harmonize_modifications
end
end

1 change: 0 additions & 1 deletion spec/dummy/app/models/widget.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
class Widget < ActiveRecord::Base
harmonize
end
Loading

0 comments on commit 8bf12b8

Please sign in to comment.