airblade-acts-as-importable 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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