immigrant 0.1.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.
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jon Jensen
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.rdoc ADDED
@@ -0,0 +1,40 @@
1
+ = Immigrant
2
+
3
+ Immigrant gives {Foreigner}[https://github.com/matthuhiggins/foreigner] a
4
+ migration generator so you can effortlessly add missing foreign keys. This is
5
+ particularly helpful when you decide to add keys to an established Rails app.
6
+
7
+ Like Foreigner, Immigrant requires Rails 3.0 or greater.
8
+
9
+ == Installation
10
+
11
+ Add the following to your Gemfile:
12
+
13
+ gem 'immigrant'
14
+
15
+ == Usage
16
+
17
+ rails generate immigration AddKeys
18
+
19
+ This will create a migration named AddKeys which will have add_foreign_key
20
+ statements for any missing foreign keys. Immigrant infers missing ones by
21
+ evaluating the associations in your models (e.g. belongs_to, has_many, etc.).
22
+ Only missing keys will be added; existing ones will never be altered or
23
+ removed.
24
+
25
+ == Considerations
26
+
27
+ If the data in your tables is bad, then the migration will fail to run
28
+ (obviously). IOW, ensure you don't have orphaned records *before* you try to
29
+ add foreign keys.
30
+
31
+ == Known Issues
32
+
33
+ Immigrant currently only looks for foreign keys in ActiveRecord::Base's
34
+ database. So if a model is using a different database connection and it has
35
+ foreign keys, Immigrant will incorrectly include them again in the generated
36
+ migration.
37
+
38
+ == License
39
+
40
+ Copyright (c) 2012 Jon Jensen, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'rake'
2
+
3
+ desc 'Default: run unit tests.'
4
+ task :default => :test
5
+
6
+ require 'rake/testtask'
7
+ desc 'Test the immigrant plugin.'
8
+ Rake::TestTask.new(:test) do |t|
9
+ t.libs << 'lib'
10
+ t.libs << 'test'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
@@ -0,0 +1,19 @@
1
+ Description:
2
+ Creates a new foreign key migration based on your current associations.
3
+ Pass the migration name, either CamelCased or under_scored.
4
+
5
+ A migration class is generated in db/migrate prefixed by a timestamp of the
6
+ current date and time. It will contain add_foreign_key calls to create any
7
+ foreign keys that do not already exist (inferred from your model
8
+ associations and current foreign keys in the database). If there are no
9
+ missing foreign keys, no migration will be created.
10
+
11
+ Example:
12
+ `rails generate immigration AddMissingForeignKeys`
13
+
14
+ If the current date is Apr 1, 2012 and the current time 02:03:04, this
15
+ creates the AddMissingForeignKeys migration
16
+ db/migrate/20120401020304_add_missing_foreign_keys.rb with appropriate
17
+ add_foreign_key calls in the Change migration. If on Rails < 3.1, they
18
+ will be in the Up migration, and corresponding remove_foreign_key calls
19
+ will be in the Down migration.
@@ -0,0 +1,20 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ class ImmigrationGenerator < ActiveRecord::Generators::Base
4
+ def create_immigration_file
5
+ @keys, warnings = Immigrant.infer_keys
6
+ warnings.values.each{ |warning| $stderr.puts "WARNING: #{warning}" }
7
+ @keys.each do |key|
8
+ next unless key.options[:dependent] == :delete
9
+ $stderr.puts "NOTICE: #{key.options[:name]} has ON DELETE CASCADE. You should remove the :dependent option from the association to take advantage of this."
10
+ end
11
+ if @keys.present?
12
+ template = ActiveRecord::VERSION::STRING < "3.1." ? "immigration-pre-3.1.rb" : "immigration.rb"
13
+ migration_template template, "db/migrate/#{file_name}.rb"
14
+ else
15
+ puts "Nothing to do"
16
+ end
17
+ end
18
+
19
+ source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
20
+ end
@@ -0,0 +1,13 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ <% @keys.each do |key| -%>
4
+ <%= key.to_ruby(:add) %>
5
+ <%- end -%>
6
+ end
7
+
8
+ def self.down
9
+ <% @keys.each do |key| -%>
10
+ <%= key.to_ruby(:remove) %>
11
+ <%- end -%>
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def change
3
+ <% @keys.each do |key| -%>
4
+ <%= key.to_ruby(:add) %>
5
+ <%- end -%>
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ module Immigrant
2
+ # add some useful stuff to foreigner's ForeignKeyDefinition
3
+ # TODO: get it in foreigner so we don't need to monkey patch
4
+ module ForeignKeyDefinition
5
+ def initialize(from_table, to_table, options, *args)
6
+ options ||= {}
7
+ options[:name] ||= "#{from_table}_#{options[:column]}_fk"
8
+ super(from_table, to_table, options, *args)
9
+ end
10
+
11
+ def hash_key
12
+ [from_table, options[:column]]
13
+ end
14
+
15
+ def to_ruby(action = :add)
16
+ if action == :add
17
+ # not DRY ... guts of this are copied from Foreigner :(
18
+ parts = [ ('add_foreign_key ' + from_table.inspect) ]
19
+ parts << to_table.inspect
20
+ parts << (':name => ' + options[:name].inspect)
21
+
22
+ if options[:column] != "#{to_table.singularize}_id"
23
+ parts << (':column => ' + options[:column].inspect)
24
+ end
25
+ if options[:primary_key] != 'id'
26
+ parts << (':primary_key => ' + options[:primary_key].inspect)
27
+ end
28
+ if options[:dependent].present?
29
+ parts << (':dependent => ' + options[:dependent].inspect)
30
+ end
31
+ parts.join(', ')
32
+ else
33
+ "remove_foreign_key #{from_table.inspect}, " \
34
+ ":name => #{options[:name].inspect}"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,7 @@
1
+ module Immigrant
2
+ def self.load
3
+ Foreigner::ConnectionAdapters::ForeignKeyDefinition.instance_eval do
4
+ include Immigrant::ForeignKeyDefinition
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module Immigrant
2
+ class Railtie < Rails::Railtie
3
+ initializer 'immigrant.load' do
4
+ # TODO: implement hook in Foreigner and use that instead
5
+ ActiveSupport.on_load :active_record do
6
+ Immigrant.load
7
+ end
8
+ end
9
+ generators do
10
+ require "generators/immigration_generator"
11
+ end
12
+ end
13
+ end
data/lib/immigrant.rb ADDED
@@ -0,0 +1,141 @@
1
+ require 'active_support/all'
2
+ require 'foreigner'
3
+
4
+ module Immigrant
5
+ extend ActiveSupport::Autoload
6
+ autoload :Loader
7
+ autoload :ForeignKeyDefinition
8
+
9
+ class << self
10
+ def infer_keys(db_keys = current_foreign_keys, classes = model_classes)
11
+ database_keys = db_keys.inject({}) { |hash, foreign_key|
12
+ hash[foreign_key.hash_key] = foreign_key
13
+ hash
14
+ }
15
+ model_keys, warnings = model_keys(classes)
16
+ new_keys = []
17
+ model_keys.keys.each do |hash_key|
18
+ foreign_key = model_keys[hash_key]
19
+ # if the foreign key exists in the db, we call it good (even if
20
+ # the name is different or :dependent doesn't match), though
21
+ # we do warn on clearly broken stuff
22
+ if current_key = database_keys[hash_key]
23
+ if current_key.to_table != foreign_key.to_table || current_key.options[:primary_key] != foreign_key.options[:primary_key]
24
+ warnings[hash_key] = "Skipping #{foreign_key.from_table}.#{foreign_key.options[:column]}: its association references a different key/table than its current foreign key"
25
+ end
26
+ else
27
+ new_keys << foreign_key
28
+ end
29
+ end
30
+ [new_keys.sort_by{ |key| key.options[:name] }, warnings]
31
+ end
32
+
33
+ private
34
+
35
+ def current_foreign_keys
36
+ ActiveRecord::Base.connection.tables.map{ |table|
37
+ ActiveRecord::Base.connection.foreign_keys(table)
38
+ }.flatten
39
+ end
40
+
41
+ def model_classes
42
+ classes = []
43
+ pattern = /^\s*(has_one|has_many|has_and_belongs_to_many|belongs_to)\s/
44
+ Dir['app/models/*.rb'].each do |model|
45
+ class_name = model.sub(/\A.*\/(.*?)\.rb\z/, '\1').camelize
46
+ begin
47
+ klass = class_name.constantize
48
+ rescue SyntaxError, LoadError
49
+ if File.read(model) =~ pattern
50
+ raise "unable to load #{class_name} and its associations"
51
+ end
52
+ next
53
+ end
54
+ classes << klass if klass < ActiveRecord::Base
55
+ end
56
+ classes
57
+ end
58
+
59
+ def model_keys(classes)
60
+ # see what the models say there should be
61
+ foreign_keys = {}
62
+ warnings = {}
63
+ classes.map{ |klass|
64
+ foreign_keys_for(klass)
65
+ }.flatten.uniq.each do |foreign_key|
66
+ # we may have inferred it from several different places, e.g.
67
+ # Bar.belongs_to :foo
68
+ # Foo.has_many :bars
69
+ # Foo.has_many :bazzes, :class_name => Bar
70
+ # we need to make sure everything is legit and see if any of them
71
+ # specify :dependent => :delete
72
+ if current_key = foreign_keys[foreign_key.hash_key]
73
+ if current_key.to_table != foreign_key.to_table || current_key.options[:primary_key] != foreign_key.options[:primary_key]
74
+ warnings[foreign_key.hash_key] ||= "Skipping #{foreign_key.from_table}.#{foreign_key.options[:column]}: it has multiple associations referencing different keys/tables."
75
+ next
76
+ else
77
+ next unless foreign_key.options[:dependent]
78
+ end
79
+ end
80
+ foreign_keys[foreign_key.hash_key] = foreign_key
81
+ end
82
+ warnings.keys.each { |hash_key| foreign_keys.delete(hash_key) }
83
+ [foreign_keys, warnings]
84
+ end
85
+
86
+ def foreign_keys_for(klass)
87
+ fk_method = ActiveRecord::VERSION::STRING < '3.1.' ? :primary_key_name : :foreign_key
88
+
89
+ klass.reflections.values.reject{ |reflection|
90
+ # some associations can just be ignored, since:
91
+ # 1. we aren't going to parse SQL
92
+ # 2. foreign keys for :through associations will be handled by their
93
+ # component has_one/has_many/belongs_to associations
94
+ # 3. :polymorphic(/:as) associations can't have foreign keys
95
+ (reflection.options.keys & [:finder_sql, :through, :polymorphic, :as]).present?
96
+ }.map { |reflection|
97
+ begin
98
+ case reflection.macro
99
+ when :belongs_to
100
+ Foreigner::ConnectionAdapters::ForeignKeyDefinition.new(
101
+ klass.table_name, reflection.klass.table_name,
102
+ :column => reflection.send(fk_method).to_s,
103
+ :primary_key => reflection.klass.primary_key.to_s,
104
+ # although belongs_to can specify :dependent, it doesn't make
105
+ # sense from a foreign key perspective
106
+ :dependent => nil
107
+ )
108
+ when :has_one, :has_many
109
+ Foreigner::ConnectionAdapters::ForeignKeyDefinition.new(
110
+ reflection.klass.table_name, klass.table_name,
111
+ :column => reflection.send(fk_method).to_s,
112
+ :primary_key => klass.primary_key.to_s,
113
+ :dependent => [:delete, :delete_all].include?(reflection.options[:dependent]) && reflection.options[:conditions].nil? ? :delete : nil
114
+ )
115
+ when :has_and_belongs_to_many
116
+ [
117
+ Foreigner::ConnectionAdapters::ForeignKeyDefinition.new(
118
+ reflection.options[:join_table], klass.table_name,
119
+ :column => reflection.send(fk_method).to_s,
120
+ :primary_key => klass.primary_key.to_s,
121
+ :dependent => nil
122
+ ),
123
+ Foreigner::ConnectionAdapters::ForeignKeyDefinition.new(
124
+ reflection.options[:join_table], reflection.klass.table_name,
125
+ :column => reflection.association_foreign_key.to_s,
126
+ :primary_key => reflection.klass.primary_key.to_s,
127
+ :dependent => nil
128
+ )
129
+ ]
130
+ end
131
+ rescue NameError # e.g. belongs_to :oops_this_is_not_a_table
132
+ []
133
+ end
134
+ }.flatten
135
+ end
136
+
137
+ end
138
+ end
139
+
140
+ require 'immigrant/loader'
141
+ require 'immigrant/railtie' if defined?(Rails)
data/test/helper.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'bundler/setup'
2
+ Bundler.require(:default)
3
+
4
+ require 'test/unit'
5
+ require 'active_record'
6
+
7
+ require 'foreigner'
8
+ class Foreigner::Adapter
9
+ def self.configured_name; "dummy_adapter"; end
10
+ end
11
+ Foreigner.load
12
+
13
+ require 'immigrant'
14
+ Immigrant.load
15
+
16
+ module TestMethods
17
+ def foreign_key_definition(*args)
18
+ Foreigner::ConnectionAdapters::ForeignKeyDefinition.new(*args)
19
+ end
20
+ end
@@ -0,0 +1,314 @@
1
+ require 'helper'
2
+
3
+ class ImmigrantTest < ActiveSupport::TestCase
4
+ include TestMethods
5
+
6
+ class MockModel < ActiveRecord::Base
7
+ self.abstract_class = true
8
+ class << self
9
+ def primary_key
10
+ connection.primary_key(table_name)
11
+ end
12
+ def connection
13
+ @connection ||= MockConnection.new
14
+ end
15
+ end
16
+ end
17
+
18
+ class MockConnection
19
+ def supports_primary_key? # AR <3.2
20
+ true
21
+ end
22
+ def primary_key(table)
23
+ table !~ /s_.*s\z/ ? 'id' : nil
24
+ end
25
+ end
26
+
27
+ def teardown
28
+ subclasses = ActiveSupport::DescendantsTracker.direct_descendants(MockModel)
29
+ subclasses.each do |subclass|
30
+ ImmigrantTest.send(:remove_const, subclass.to_s.sub(/.*::/, ''))
31
+ end
32
+ subclasses.replace([])
33
+ end
34
+
35
+
36
+ # basic scenarios
37
+
38
+ test 'belongs_to should generate a foreign key' do
39
+ class Author < MockModel; end
40
+ class Book < MockModel
41
+ belongs_to :guy, :class_name => 'Author', :foreign_key => 'author_id'
42
+ end
43
+
44
+ assert_equal(
45
+ [foreign_key_definition(
46
+ 'books', 'authors',
47
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
48
+ )],
49
+ Immigrant.infer_keys([], [Author, Book]).first
50
+ )
51
+ end
52
+
53
+ test 'has_one should generate a foreign key' do
54
+ class Author < MockModel
55
+ has_one :piece_de_resistance, :class_name => 'Book', :order => "id DESC"
56
+ end
57
+ class Book < MockModel; end
58
+
59
+ assert_equal(
60
+ [foreign_key_definition(
61
+ 'books', 'authors',
62
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
63
+ )],
64
+ Immigrant.infer_keys([], [Author, Book]).first
65
+ )
66
+ end
67
+
68
+ test 'has_one :dependent => :delete should generate a foreign key with :dependent => :delete' do
69
+ class Author < MockModel
70
+ has_one :book, :order => "id DESC", :dependent => :delete
71
+ end
72
+ class Book < MockModel; end
73
+
74
+ assert_equal(
75
+ [foreign_key_definition(
76
+ 'books', 'authors',
77
+ :column => 'author_id', :primary_key => 'id', :dependent => :delete
78
+ )],
79
+ Immigrant.infer_keys([], [Author, Book]).first
80
+ )
81
+ end
82
+
83
+ test 'has_many should generate a foreign key' do
84
+ class Author < MockModel
85
+ has_many :babies, :class_name => 'Book'
86
+ end
87
+ class Book < MockModel; end
88
+
89
+ assert_equal(
90
+ [foreign_key_definition(
91
+ 'books', 'authors',
92
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
93
+ )],
94
+ Immigrant.infer_keys([], [Author, Book]).first
95
+ )
96
+ end
97
+
98
+ test 'has_many :dependent => :delete_all should generate a foreign key with :dependent => :delete' do
99
+ class Author < MockModel
100
+ has_many :books, :dependent => :delete_all
101
+ end
102
+ class Book < MockModel; end
103
+
104
+ assert_equal(
105
+ [foreign_key_definition(
106
+ 'books', 'authors',
107
+ :column => 'author_id', :primary_key => 'id', :dependent => :delete
108
+ )],
109
+ Immigrant.infer_keys([], [Author, Book]).first
110
+ )
111
+ end
112
+
113
+ test 'has_and_belongs_to_many should generate two foreign keys' do
114
+ class Author < MockModel
115
+ has_and_belongs_to_many :fans
116
+ end
117
+ class Fan < MockModel; end
118
+
119
+ assert_equal(
120
+ [foreign_key_definition(
121
+ 'authors_fans', 'authors',
122
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
123
+ ),
124
+ foreign_key_definition(
125
+ 'authors_fans', 'fans',
126
+ :column => 'fan_id', :primary_key => 'id', :dependent => nil
127
+ )],
128
+ Immigrant.infer_keys([], [Author, Fan]).first
129
+ )
130
+ end
131
+
132
+ test 'conditional has_one/has_many associations should ignore :dependent' do
133
+ class Author < MockModel
134
+ has_many :articles, :conditions => "published", :dependent => :delete_all
135
+ has_one :favorite_book, :class_name => 'Book',
136
+ :conditions => "most_awesome", :dependent => :delete
137
+ end
138
+ class Book < MockModel; end
139
+ class Article < MockModel; end
140
+
141
+ assert_equal(
142
+ [foreign_key_definition(
143
+ 'articles', 'authors',
144
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
145
+ ),
146
+ foreign_key_definition(
147
+ 'books', 'authors',
148
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
149
+ )],
150
+ Immigrant.infer_keys([], [Article, Author, Book]).first
151
+ )
152
+ end
153
+
154
+
155
+ # (no) duplication
156
+
157
+ test 'STI should not generate duplicate foreign keys' do
158
+ class Company < MockModel; end
159
+ class Employee < MockModel
160
+ belongs_to :company
161
+ end
162
+ class Manager < Employee; end
163
+
164
+ assert(Manager.reflections.present?)
165
+ assert_equal(
166
+ [foreign_key_definition(
167
+ 'employees', 'companies',
168
+ :column => 'company_id', :primary_key => 'id', :dependent => nil
169
+ )],
170
+ Immigrant.infer_keys([], [Company, Employee, Manager]).first
171
+ )
172
+ end
173
+
174
+ test 'complementary associations should not generate duplicate foreign keys' do
175
+ class Author < MockModel
176
+ has_many :books
177
+ end
178
+ class Book < MockModel
179
+ belongs_to :author
180
+ end
181
+
182
+ assert_equal(
183
+ [foreign_key_definition(
184
+ 'books', 'authors',
185
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
186
+ )],
187
+ Immigrant.infer_keys([], [Author, Book]).first
188
+ )
189
+ end
190
+
191
+ test 'redundant associations should not generate duplicate foreign keys' do
192
+ class Author < MockModel
193
+ has_many :books
194
+ has_many :favorite_books, :class_name => 'Book', :conditions => "awesome"
195
+ has_many :bad_books, :class_name => 'Book', :conditions => "amateur_hour"
196
+ end
197
+ class Book < MockModel; end
198
+
199
+ assert_equal(
200
+ [foreign_key_definition(
201
+ 'books', 'authors',
202
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
203
+ )],
204
+ Immigrant.infer_keys([], [Author, Book]).first
205
+ )
206
+ end
207
+
208
+
209
+ # skipped associations
210
+
211
+ test 'associations should not generate foreign keys if they already exist, even if :dependent/name are different' do
212
+ database_keys = [
213
+ foreign_key_definition(
214
+ 'articles', 'authors',
215
+ :column => 'author_id', :primary_key => 'id', :dependent => nil,
216
+ :name => "doesn't_matter"
217
+ ),
218
+ foreign_key_definition(
219
+ 'books', 'authors', :column => 'author_id', :primary_key => 'id',
220
+ :dependent => :delete
221
+ )
222
+ ]
223
+
224
+ class Author < MockModel
225
+ has_many :articles
226
+ has_one :favorite_book, :class_name => 'Book',
227
+ :conditions => "most_awesome"
228
+ end
229
+ class Book < MockModel; end
230
+ class Article < MockModel; end
231
+
232
+ assert_equal(
233
+ [],
234
+ Immigrant.infer_keys(database_keys, [Article, Author, Book]).first
235
+ )
236
+ end
237
+
238
+ test 'finder_sql associations should not generate foreign keys' do
239
+ class Author < MockModel
240
+ has_many :books, :finder_sql => <<-SQL
241
+ SELECT *
242
+ FROM books
243
+ WHERE author_id = \#{id}
244
+ ORDER BY RANDOM() LIMIT 5'
245
+ SQL
246
+ end
247
+ class Book < MockModel; end
248
+
249
+ assert_equal(
250
+ [],
251
+ Immigrant.infer_keys([], [Author, Book]).first
252
+ )
253
+ end
254
+
255
+ test 'polymorphic associations should not generate foreign keys' do
256
+ class Property < MockModel
257
+ belongs_to :owner, :polymorphic => true
258
+ end
259
+ class Person < MockModel
260
+ has_many :properties, :as => :owner
261
+ end
262
+ class Corporation < MockModel
263
+ has_many :properties, :as => :owner
264
+ end
265
+
266
+ assert_equal(
267
+ [],
268
+ Immigrant.infer_keys([], [Corporation, Person, Property]).first
269
+ )
270
+ end
271
+
272
+ test 'has_many :through should not generate foreign keys' do
273
+ class Author < MockModel
274
+ has_many :authors_fans
275
+ has_many :fans, :through => :authors_fans
276
+ end
277
+ class AuthorsFan < MockModel
278
+ belongs_to :author
279
+ belongs_to :fan
280
+ end
281
+ class Fan < MockModel
282
+ has_many :authors_fans
283
+ has_many :authors, :through => :authors_fans
284
+ end
285
+
286
+ assert_equal(
287
+ [foreign_key_definition(
288
+ 'authors_fans', 'authors',
289
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
290
+ ),
291
+ foreign_key_definition(
292
+ 'authors_fans', 'fans',
293
+ :column => 'fan_id', :primary_key => 'id', :dependent => nil
294
+ )],
295
+ Immigrant.infer_keys([], [Author, AuthorsFan, Fan]).first
296
+ )
297
+ end
298
+
299
+ test 'broken associations should not cause errors' do
300
+ class Author < MockModel; end
301
+ class Book < MockModel
302
+ belongs_to :author
303
+ belongs_to :invalid
304
+ end
305
+
306
+ assert_equal(
307
+ [foreign_key_definition(
308
+ 'books', 'authors',
309
+ :column => 'author_id', :primary_key => 'id', :dependent => nil
310
+ )],
311
+ Immigrant.infer_keys([], [Author, Book]).first
312
+ )
313
+ end
314
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: immigrant
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Jon Jensen
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-04-01 00:00:00 -06: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
+ - !ruby/object:Gem::Dependency
37
+ name: foreigner
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 21
45
+ segments:
46
+ - 1
47
+ - 1
48
+ - 3
49
+ version: 1.1.3
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: Adds a generator for creating a foreign key migration based on your current model associations
53
+ email: jenseng@gmail.com
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files:
59
+ - README.rdoc
60
+ files:
61
+ - LICENSE.txt
62
+ - Rakefile
63
+ - README.rdoc
64
+ - lib/generators/USAGE
65
+ - lib/generators/immigration_generator.rb
66
+ - lib/generators/templates/immigration-pre-3.1.rb
67
+ - lib/generators/templates/immigration.rb
68
+ - lib/immigrant/foreign_key_definition.rb
69
+ - lib/immigrant/loader.rb
70
+ - lib/immigrant/railtie.rb
71
+ - lib/immigrant.rb
72
+ - test/helper.rb
73
+ - test/immigrant_test.rb
74
+ has_rdoc: true
75
+ homepage: http://github.com/jenseng/immigrant
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options: []
80
+
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 57
89
+ segments:
90
+ - 1
91
+ - 8
92
+ - 7
93
+ version: 1.8.7
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 17
100
+ segments:
101
+ - 1
102
+ - 3
103
+ - 5
104
+ version: 1.3.5
105
+ requirements: []
106
+
107
+ rubyforge_project:
108
+ rubygems_version: 1.6.2
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Migration generator for Foreigner
112
+ test_files: []
113
+