airblade-acts-as-importable 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ spec/debug.log
2
+ pkg/
data/Gemfile ADDED
@@ -0,0 +1 @@
1
+ gemspec
@@ -0,0 +1,5 @@
1
+ h1. Acts as Importable
2
+
3
+ See my article "Importing Legacy Data in Rails":http://openmonkey.com/articles/2009/05/importing-legacy-data-in-rails for an introduction and documentation.
4
+
5
+ Copyright (c) 2009 Tim Riley, released under the MIT license
@@ -0,0 +1,25 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+ require 'rake/rdoctask'
4
+
5
+ require 'bundler'
6
+ Bundler::GemHelper.install_tasks
7
+
8
+ desc 'Default: run specs.'
9
+ task :default => :spec
10
+
11
+ desc 'Test the acts_as_importable plugin'
12
+ Spec::Rake::SpecTask.new(:spec) do |t|
13
+ t.libs << 'lib'
14
+ t.pattern = 'spec/**/*_spec.rb'
15
+ t.verbose = true
16
+ end
17
+
18
+ desc 'Generate documentation for the acts_as_audited plugin.'
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'doc'
21
+ rdoc.title = 'acts_as_importable'
22
+ rdoc.options << '--line-numbers' << '--inline-source'
23
+ rdoc.rdoc_files.include('README')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "lib/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "airblade-acts-as-importable"
7
+ s.version = AMC::Acts::Importable::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['Tim Riley, Andrew Stewart']
10
+ s.email = ['boss@airbladesoftware.com']
11
+ s.homepage = ''
12
+ s.summary = ''
13
+ s.description = s.summary
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency 'activerecord', '~> 3.0'
21
+
22
+ #s.add_development_dependency '', ''
23
+ end
data/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ $:.unshift "#{File.dirname(__FILE__)}/lib"
2
+ require 'core_extensions'
3
+ require 'acts_as_importable'
4
+ ActiveRecord::Base.class_eval { include AMC::Acts::Importable }
@@ -0,0 +1,82 @@
1
+ module AMC
2
+ module Acts
3
+ module Importable
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ def acts_as_importable(options = {})
12
+ # Store the import target class with the legacy class
13
+ write_inheritable_attribute :importable_to, options[:to]
14
+
15
+ # Don't extend or include twice. This will allow acts_as_importable to be called multiple times.
16
+ # eg. once in a parent class and once again in the child class, where it can override some options.
17
+ extend AMC::Acts::Importable::SingletonMethods unless self.methods.include?('import') && self.methods.include?('import_all')
18
+ include AMC::Acts::Importable::InstanceMethods unless self.included_modules.include?(AMC::Acts::Importable::InstanceMethods)
19
+ end
20
+
21
+ end # ClassMethods
22
+
23
+ module SingletonMethods
24
+ def import(id)
25
+ find(id).import
26
+ end
27
+
28
+ def import_all(*args)
29
+ all(*args).each do |legacy_model|
30
+ legacy_model.import
31
+ end
32
+ end
33
+
34
+ # This requires a numeric primary key for the legacy tables
35
+ def import_all_in_batches
36
+ each do |legacy_model|
37
+ legacy_model.import
38
+ end
39
+ end
40
+
41
+ def lookup(id)
42
+ lookup_class = read_inheritable_attribute(:importable_to) || "#{self.to_s.split('::').last}"
43
+ lookups[id] ||= Kernel.const_get(lookup_class).first(:conditions => {:legacy_id => id, :legacy_class => self.to_s}).try(:id__)
44
+ end
45
+
46
+ def flush_lookups!
47
+ @lookups = {}
48
+ end
49
+
50
+ private
51
+
52
+ def lookups
53
+ @lookups ||= {}
54
+ end
55
+
56
+ end # SingletonMethods
57
+
58
+ module InstanceMethods
59
+
60
+ def import
61
+ returning to_model do |new_model|
62
+ if new_model
63
+ new_model.legacy_id = self.id if new_model.respond_to?(:"legacy_id=")
64
+ new_model.legacy_class = self.class.to_s if new_model.respond_to?(:"legacy_class=")
65
+
66
+ if !new_model.save
67
+ p new_model.errors
68
+ # TODO log an error that the model failed to save
69
+ # TODO remove the raise once we're out of the development cycle
70
+ raise
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end # InstanceMethods
76
+
77
+ end
78
+ end
79
+ end
80
+
81
+ require 'core_extensions'
82
+ ActiveRecord::Base.class_eval { include AMC::Acts::Importable }
@@ -0,0 +1,18 @@
1
+ # See http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/212639
2
+ # And http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/210633 for alternative [:id] notation
3
+ class ActiveRecord::Base
4
+ alias_method :id__, :id if method_defined? :id
5
+ end
6
+
7
+
8
+ # From http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord
9
+ class <<ActiveRecord::Base
10
+ def each(limit=1000)
11
+ rows = find(:all, :conditions => ["#{primary_key} > ?", 0], :limit => limit)
12
+ while rows.any?
13
+ rows.each { |record| yield record }
14
+ rows = find(:all, :conditions => ["#{primary_key} > ?", rows.last.id], :limit => limit)
15
+ end
16
+ self
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module AMC
2
+ module Acts
3
+ module Importable
4
+ VERSION = '1.0.0'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,117 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe AMC::Acts::Importable do
4
+
5
+ it "should include instance methods" do
6
+ Legacy::Thing.new.should be_kind_of(AMC::Acts::Importable::InstanceMethods)
7
+ end
8
+
9
+ it "should extend singleton methods" do
10
+ Legacy::Thing.should be_kind_of(AMC::Acts::Importable::SingletonMethods)
11
+ end
12
+
13
+ describe "importing from an instance of a single model" do
14
+ before(:each) do
15
+ @legacy_thing = create_legacy_thing
16
+ end
17
+
18
+ it "should build the new model using the legacy model's to_model method" do
19
+ @legacy_thing.should_receive(:to_model)
20
+ @legacy_thing.import
21
+ end
22
+
23
+ it "should assign the legacy model's ID to the legacy_id attribute in the new model" do
24
+ new_model = @legacy_thing.import
25
+ new_model.legacy_id.should == @legacy_thing.id
26
+ end
27
+
28
+ it "should assign the legacy model's class name to the legacy_class attribute of the new model" do
29
+ new_model = @legacy_thing.import
30
+ new_model.legacy_class.should == @legacy_thing.class.to_s
31
+ end
32
+
33
+ it "should save the new model" do
34
+ new_model = @legacy_thing.import
35
+ new_model.should_not be_new_record
36
+ end
37
+ end
38
+
39
+ describe "importing a single model" do
40
+ before(:each) do
41
+ @legacy_thing = mock(Legacy::Thing, :import => nil)
42
+ Legacy::Thing.stub!(:find).and_return(@legacy_thing)
43
+ end
44
+
45
+ it "should find the legacy model" do
46
+ Legacy::Thing.should_receive(:find).with(123)
47
+ Legacy::Thing.import(123)
48
+ end
49
+
50
+ it "should import the legacy model" do
51
+ @legacy_thing.should_receive(:import)
52
+ Legacy::Thing.import(123)
53
+ end
54
+ end
55
+
56
+ describe "importing all models" do
57
+ before(:each) do
58
+ @legacy_thing_1 = mock(Legacy::Thing, :import => nil)
59
+ @legacy_thing_2 = mock(Legacy::Thing, :import => nil)
60
+ Legacy::Thing.stub!(:all).and_return([@legacy_thing_1, @legacy_thing_2])
61
+ end
62
+
63
+ it "should find all legacy models" do
64
+ Legacy::Thing.should_receive(:all)
65
+ Legacy::Thing.import_all
66
+ end
67
+
68
+ it "should import each of the legacy models" do
69
+ @legacy_thing_1.should_receive(:import)
70
+ @legacy_thing_2.should_receive(:import)
71
+ Legacy::Thing.import_all
72
+ end
73
+ end
74
+
75
+ describe "looking up legcy IDs for already imported models" do
76
+ before(:all) do
77
+ @legacy_thing = create_legacy_thing
78
+ @imported_thing = @legacy_thing.import
79
+ end
80
+
81
+ before(:each) do
82
+ Legacy::Thing.flush_lookups!
83
+ end
84
+
85
+ it "should attempt to find the imported model by legacy_id" do
86
+ Thing.should_receive(:first).with(:conditions => {:legacy_id => 123, :legacy_class => 'Legacy::Thing'})
87
+ Legacy::Thing.lookup(123)
88
+ end
89
+
90
+ it "should return the ID of the imported model with a matching legacy_id" do
91
+ Legacy::Thing.lookup(@legacy_thing.id).should == @imported_thing.id
92
+ end
93
+
94
+ it "should memoize the mapping from the legacy_id to the imported model's ID" do
95
+ Thing.should_receive(:first).and_return(@imported_thing)
96
+ Legacy::Thing.lookup(@legacy_thing.id).inspect
97
+ Thing.should_not_receive(:find_by_legacy_id)
98
+ Legacy::Thing.lookup(@legacy_thing.id).inspect
99
+ end
100
+ end
101
+
102
+ describe "looking up legacy IDs for already imported models when importing to a model with a different class name to the legacy model" do
103
+ before(:all) do
104
+ @other_legacy_thing = create_other_legacy_thing
105
+ end
106
+
107
+ before(:each) do
108
+ Legacy::Thing.flush_lookups!
109
+ end
110
+
111
+ it "should attempt to find the imported model with the specified class name by legacy_id" do
112
+ Thing.should_receive(:first).with(:conditions => {:legacy_id => 123, :legacy_class => 'Legacy::OtherThing'})
113
+ Legacy::OtherThing.lookup(123)
114
+ end
115
+ end
116
+
117
+ end
@@ -0,0 +1,21 @@
1
+ sqlite:
2
+ :adapter: sqlite
3
+ :dbfile: acts_as_importable_plugin.sqlite.db
4
+ sqlite3mem:
5
+ :adapter: sqlite3
6
+ :dbfile: ":memory:"
7
+ sqlite3:
8
+ :adapter: sqlite3
9
+ :dbfile: acts_as_importable_plugin.sqlite3.db
10
+ postgresql:
11
+ :adapter: postgresql
12
+ :username: postgres
13
+ :password: postgres
14
+ :database: acts_as_importable_plugin_test
15
+ :min_messages: ERROR
16
+ mysql:
17
+ :adapter: mysql
18
+ :host: localhost
19
+ :username: rails
20
+ :password:
21
+ :database: acts_as_importable_plugin_test
@@ -0,0 +1,13 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :legacy_things, :force => true do |t|
3
+ t.string :legacy_name
4
+ t.string :legacy_description
5
+ end
6
+
7
+ create_table :things, :force => true do |t|
8
+ t.string :name
9
+ t.string :description
10
+ t.integer :legacy_id
11
+ t.string :legacy_class
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,50 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'active_record'
6
+ require File.dirname(__FILE__) + '/../init.rb'
7
+
8
+ require 'spec'
9
+
10
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
11
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
12
+ ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3mem'])
13
+
14
+ ActiveRecord::Migration.verbose = false
15
+ load(File.dirname(__FILE__) + "/schema.rb")
16
+
17
+ class Thing < ActiveRecord::Base
18
+ end
19
+
20
+ module Legacy
21
+ class Thing < ActiveRecord::Base
22
+ set_table_name 'legacy_things'
23
+
24
+ acts_as_importable
25
+
26
+ def to_model
27
+ ::Thing.new do |t|
28
+ t.name = self.legacy_name
29
+ t.description = self.legacy_description
30
+ end
31
+ end
32
+ end
33
+
34
+ class OtherThing < Thing
35
+ set_table_name 'legacy_things'
36
+
37
+ acts_as_importable :to => 'Thing'
38
+ end
39
+ end
40
+
41
+ Spec::Runner.configure do |config|
42
+
43
+ def create_legacy_thing(attrs = {})
44
+ Legacy::Thing.create({:legacy_name => 'Grandfather Clock', :legacy_description => 'Tick tock'}.merge(attrs))
45
+ end
46
+
47
+ def create_other_legacy_thing(attrs = {})
48
+ Legacy::OtherThing.create({:legacy_name => 'Grandfather Clock', :legacy_description => 'Tick tock'}.merge(attrs))
49
+ end
50
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :acts_as_importable do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: airblade-acts-as-importable
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Tim Riley, Andrew Stewart
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-02 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activerecord
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 3
32
+ - 0
33
+ version: "3.0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ description: ""
37
+ email:
38
+ - boss@airbladesoftware.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - .gitignore
47
+ - Gemfile
48
+ - README.textile
49
+ - Rakefile
50
+ - airblade-acts-as-importable.gemspec
51
+ - init.rb
52
+ - lib/acts_as_importable.rb
53
+ - lib/core_extensions.rb
54
+ - lib/version.rb
55
+ - spec/acts_as_importable_spec.rb
56
+ - spec/database.yml
57
+ - spec/schema.rb
58
+ - spec/spec.opts
59
+ - spec/spec_helper.rb
60
+ - tasks/acts_as_importable_tasks.rake
61
+ has_rdoc: true
62
+ homepage: ""
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options: []
67
+
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ requirements: []
89
+
90
+ rubyforge_project:
91
+ rubygems_version: 1.3.7
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: ""
95
+ test_files:
96
+ - spec/acts_as_importable_spec.rb
97
+ - spec/database.yml
98
+ - spec/schema.rb
99
+ - spec/spec.opts
100
+ - spec/spec_helper.rb