harmonize 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -9,17 +9,18 @@ hahr-muh-nahyz -
9
9
 
10
10
  harmonize is a rails 3 engine that allows one to harmonize entire scopes of an ActiveRecord model with arbitrary external data sources.
11
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.
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
13
 
14
14
  ## Simple Example
15
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.
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 attribute :name as the primary\_key.
17
17
 
18
18
  class Store < ActiveRecord::Base
19
19
  validates :name, :address, :presence => true
20
20
 
21
21
  # Setup a harmonizer
22
22
  harmonize do |config|
23
+ # use :name from the source data as the lookup key for target records
23
24
  config.key = :name
24
25
  end
25
26
 
@@ -31,7 +32,7 @@ Lets pretend we work for a company that has a large list of stores. This list of
31
32
  end
32
33
  end
33
34
 
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
+ 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
 
36
37
  ## Harmonize::Configuration
37
38
 
@@ -41,54 +42,72 @@ Each call to harmonize creates a Harmonize::Configuration instance that defines
41
42
  harmonize do |config|
42
43
  config.harmonizer_name = :custom_name
43
44
  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
45
+ config.source = :get_latest_source_data_feed!
46
+ config.target = :some_scope_name
50
47
  config.strategy = YourCustomStrategy
51
48
  config.strategy_arguments = { :options => 'needed', :to => 'initialize', :your => 'custom strategy' }
52
49
  end
53
50
  end
54
51
 
55
- ### Harmonizer::Configuration.harmonizer_name
52
+ ### Harmonizer::Configuration.harmonizer\_name
56
53
 
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.
54
+ 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. This option is used to name special methods used by harmonize. The harmonization method is named with the following convention: "harmonize\_#{harmonizer\_name}!".
58
55
 
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}!".
56
+ The default setting is:
57
+
58
+ :default
59
+
60
+ This setting can be any symbol.
61
61
 
62
62
  ### Harmonizer::Configuration.key
63
63
 
64
- harmonize uses the configured key to determine what attribute to use to find existing target records.
64
+ harmonize uses the configured key to determine what attribute in the source data feed to use to find existing target records.
65
+
66
+ The default setting is:
67
+
68
+ :id
65
69
 
66
- The default key is :id
70
+ This setting can be any attribute that will be found in source records.
67
71
 
68
72
  ### Harmonizer::Configuration.source
69
73
 
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.
74
+ 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. By default this setting calls a method name with the following convention: "harmonize\_source\_#{harmonizer\_name}".
71
75
 
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}".
76
+ The default setting is:
77
+
78
+ harmonize_source_default
79
+
80
+ This setting can be any class method defined in the model that returns properly structured data.
74
81
 
75
82
  ### Harmonizer::Configuration.target
76
83
 
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
84
+ 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. Hint, all (named) scopes return ActiveRecord::Relation instances.
85
+
86
+ The default setting is:
78
87
 
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.
88
+ scoped
89
+
90
+ This setting can be any class method defined in the model that returns an ActiveRecord::Relation.
80
91
 
81
92
  ### Harmonizer::Configuration.strategy
82
93
 
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.
94
+ 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.
95
+
96
+ The default setting is:
84
97
 
85
- The default strategy is Harmonize::Strategies::BasicCrudStrategy
98
+ Harmonize::Strategies::BasicCrudStrategy
99
+
100
+ This setting can be any class that returns complies with the Strategy api.
86
101
 
87
102
  ### Harmonizer::Configuration.target
88
103
 
89
- harmonize uses the configured strategy_arguments to determine which arguments to use when initializing the set Harmonize::Strategies::Strategy subclass.
104
+ harmonize uses the configured strategy\_arguments to determine which arguments to use when initializing the set Harmonize::Strategies::Strategy subclass.
105
+
106
+ The default settings is:
90
107
 
91
- The default strategy is {}.
108
+ {}
109
+
110
+ This setting can be a hash of initialize options for the defined Strategy.
92
111
 
93
112
  ## Harmonize::Strategies
94
113
 
@@ -107,7 +126,7 @@ The default harmonize strategy is Harmonize::Strategies::BasicCrudStrategy. This
107
126
 
108
127
  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
128
 
110
- ## Installation
129
+ ## Installation and Usage
111
130
 
112
131
  Add harmonize to the gem file for your rails application:
113
132
 
@@ -127,13 +146,19 @@ Configure your model to use harmonize and implement your source:
127
146
  end
128
147
  end
129
148
 
149
+ Bring your data into harmony
150
+
151
+ MyModel.harmonize!(:default)
152
+ # or the custom method named after your harmonizer
153
+ MyModel.harmonize_default!
154
+
130
155
  Use, report bugs, fix them, and send pull requests!
131
156
 
132
157
  ## Plans
133
158
 
134
159
  ## TODO
135
160
 
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.
161
+ * 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
162
 
138
163
  ## Contributors
139
164
 
@@ -21,7 +21,7 @@ module Harmonize
21
21
  end
22
22
 
23
23
  def harmonize!(harmonizer_name)
24
- raise UnknownHarmonizerName.new(harmonizer_name) unless harmonizers.has_key?(harmonizer_name)
24
+ raise UnknownHarmonizerName.new(harmonizer_name.to_s) unless harmonizers.has_key?(harmonizer_name)
25
25
  harmonizer = harmonizers[harmonizer_name]
26
26
  harmonize_log = create_harmonize_log(harmonizer_name)
27
27
  strategy = harmonizer.strategy.new(*harmonizer.strategy_arguments)
@@ -53,21 +53,47 @@ module Harmonize
53
53
  )
54
54
  end
55
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
- })
56
+ def harmonize_source_method(harmonizer_name, method_name = nil)
57
+ method_name ||= "harmonize_source_#{harmonizer_name}".to_sym
58
+ raise HarmonizerSourceUndefined.new(harmonizer_name.to_s) unless respond_to?(method_name)
59
+ send(method_name)
61
60
  end
62
61
 
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)
62
+ def harmonize_target_method(harmonizer_name, method_name = nil)
63
+ method_name ||= :scoped
64
+ raise HarmonizerTargetUndefined.new(harmonizer_name.to_s) unless respond_to?(method_name)
65
+ target_scope = send(method_name)
66
+ raise HarmonizerTargetInvalid.new(harmonizer_name.to_s) unless target_scope.is_a?(ActiveRecord::Relation)
67
+ target_scope
68
+ end
69
+
70
+ def validate_harmonize_source(configuration)
71
+ case configuration.source.class.name
72
+ when "Proc"
73
+ configuration
74
+ when "Symbol"
75
+ configuration.source = lambda { harmonize_source_method(configuration.harmonizer_name, configuration.source) }
76
+ else
77
+ configuration.source = lambda { harmonize_source_method(configuration.harmonizer_name) }
78
+ end
79
+ configuration
80
+ end
81
+
82
+ def validate_harmonize_target(configuration)
83
+ case configuration.target.class.name
84
+ when "Proc"
85
+ configuration
86
+ when "Symbol"
87
+ configuration.target = lambda { harmonize_target_method(configuration.harmonizer_name, configuration.target) }
88
+ else
89
+ configuration.target = lambda { harmonize_target_method(configuration.harmonizer_name) }
90
+ end
91
+ configuration
67
92
  end
68
93
 
69
94
  def validate_harmonizer_configuration(configuration)
70
- configuration.reverse_merge(default_harmonizer_options(configuration.harmonizer_name))
95
+ configuration = validate_harmonize_source(configuration)
96
+ configuration = validate_harmonize_target(configuration)
71
97
  end
72
98
 
73
99
  def setup_harmonizer_method(harmonizer_name)
@@ -4,4 +4,5 @@ module Harmonize
4
4
  class UnknownHarmonizerName < HarmonizeError ; end
5
5
  class HarmonizerSourceUndefined < HarmonizeError ; end
6
6
  class HarmonizerTargetUndefined < HarmonizeError ; end
7
+ class HarmonizerTargetInvalid < HarmonizeError ; end
7
8
  end
@@ -1,7 +1,7 @@
1
1
  module Harmonize
2
2
  module Gemdata
3
3
  Name = 'harmonize'
4
- Version = '0.0.1'
4
+ Version = '0.0.2'
5
5
  Authors = [ 'Bram Swenson' ]
6
6
  Email = [ 'bram@craniumisajar.com' ]
7
7
  Summary = <<-END_SUMMARY
@@ -3,67 +3,85 @@ module Harmonize
3
3
 
4
4
  class BasicCrudStrategy < Strategy
5
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
6
+ def harmonize!
7
+ touched_keys = create_and_update_targets
8
+ destroy_targets_not_found_in_source(touched_keys)
11
9
  end
12
10
 
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)
11
+ private
12
+
13
+ def create_and_update_targets
14
+ sources.each.inject([]) do |keys, source|
15
+ target_instance = find_target_instance(harmonizer.key, source[harmonizer.key])
16
+ modification = initialize_modification(target_instance)
17
+ modification = harmonize_target_instance!(target_instance, source, modification)
18
+ modification.save! if modification
19
+ keys << source[harmonizer.key]
20
+ end
18
21
  end
19
- end
20
22
 
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
23
+ def destroy_targets_not_found_in_source(touched_keys)
24
+ # if we didn't get any touched_keys, destroy everything in targets scope
25
+ destroy_scope = touched_keys.empty? ? targets : targets.where("#{harmonizer.key} NOT IN (?)", touched_keys)
26
+ destroy_scope.find_each do |instance|
27
+ modification = harmonize_log.modifications.build(
28
+ :modification_type => 'destroy', :before_time => DateTime.now,
29
+ :instance_id => instance.id
30
+ )
31
+ instance.destroy
32
+ modification.update_attributes!(:after_time => DateTime.now)
29
33
  end
30
- rescue ActiveRecord::ActiveRecordError, ActiveRecord::UnknownAttributeError => e
31
- errored = true
32
- error_message = e.message
33
34
  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)
35
+
36
+ def find_target_instance(key, value)
37
+ target_relation = targets.where(key => value)
38
+ instance = target_relation.first rescue nil
39
+ instance ||= target_relation.build
40
+ instance
38
41
  end
39
- end
40
42
 
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]
43
+ def initialize_modification(instance)
44
+ unless instance.new_record?
45
+ harmonize_log.modifications.new(:modification_type => 'update', :before_time => DateTime.now)
46
+ else
47
+ harmonize_log.modifications.new(:modification_type => 'create', :before_time => DateTime.now)
48
+ end
49
+ end
50
+
51
+ def harmonize_target_instance!(target_instance, source, modification)
52
+ target_instance, error_message = update_target_attributes(target_instance, source)
53
+ return false unless (target_instance.changed? or error_message)
54
+ target_instance, error_message = save_target(target_instance) unless error_message
55
+ if error_message
56
+ modification.attributes = { :modification_type => 'error', :instance_errors => error_message }
57
+ else
58
+ modification.attributes = { :after_time => DateTime.now, :instance_id => target_instance.id }
59
+ end
60
+ modification
47
61
  end
48
- end
49
62
 
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)
63
+ def update_target_attributes(target_instance, source)
64
+ error_message = false
65
+ begin
66
+ target_instance.attributes = source
67
+ rescue ActiveRecord::UnknownAttributeError => e
68
+ error_message = e.message
69
+ end
70
+ return target_instance, error_message
71
+ end
72
+
73
+ def save_target(target_instance)
74
+ error_message = false
75
+ begin
76
+ unless target_instance.save
77
+ error_message = target_instance.errors.full_messages
78
+ end
79
+ rescue ActiveRecord::ActiveRecordError => e
80
+ error_message = e.message
81
+ end
82
+ return target_instance, error_message
60
83
  end
61
- end
62
84
 
63
- def harmonize!
64
- touched_keys = create_and_update_targets
65
- destroy_targets_not_found_in_source(touched_keys)
66
- end
67
85
  end
68
86
  end
69
87
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: harmonize
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.1
5
+ version: 0.0.2
6
6
  platform: ruby
7
7
  authors:
8
8
  - Bram Swenson
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-06-26 00:00:00 -07:00
13
+ date: 2011-06-27 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -67,7 +67,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
67
67
  requirements:
68
68
  - - ">="
69
69
  - !ruby/object:Gem::Version
70
- hash: 2398958541788060618
70
+ hash: 3057359512407760229
71
71
  segments:
72
72
  - 0
73
73
  version: "0"