dwilkie-foreigner 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 [name of plugin creator]
1
+ Copyright (c) 2010 David Wilkie
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -18,3 +18,4 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
18
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
19
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README.textile ADDED
@@ -0,0 +1,119 @@
1
+ h1. dwilkie Foreigner
2
+
3
+ dwilkie-foreigner is a fork of "Matt Higgins Foreigner":http://github.com/matthuhiggins/foreigner/
4
+ which supports adding AND enforcing foreign key constraints on Sqlite3 databases.
5
+
6
+ h2. Some Examples
7
+
8
+ dwilkie-foreigner allows you to do the following in your migration files
9
+ <pre>
10
+ create_table :comments do |t|
11
+ t.references :posts, :foreign_key => true, :null => false
12
+ end
13
+ </pre>
14
+ Which will generate the following SQL:
15
+ <pre>
16
+ CREATE TABLE "comments" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
17
+ "post_id" integer NOT NULL,
18
+ FOREIGN KEY ("post_id") REFERENCES "posts"(id));
19
+ </pre>
20
+
21
+ Go a different column name?
22
+ <pre>
23
+ create_table :comments do |t|
24
+ t.references :article, :null => false
25
+ t.foreign_key :posts, :column => :article_id
26
+ end
27
+ </pre>
28
+ Which generates:
29
+ <pre>
30
+ CREATE TABLE "comments" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
31
+ "article_id" integer NOT NULL,
32
+ FOREIGN KEY ("article_id") REFERENCES "posts"(id));
33
+ </pre>
34
+
35
+ Want to specify a dependency (nullify or delete)?
36
+ <pre>
37
+ create_table :comments do |t|
38
+ t.references :posts, :foreign_key => {:dependent => :delete}, :null => false
39
+ end
40
+ </pre>
41
+ Generates:
42
+ <pre>
43
+ CREATE TABLE "comments" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
44
+ "post_id" integer NOT NULL,
45
+ FOREIGN KEY ("post_id") REFERENCES "posts"(id) ON DELETE CASCADE);
46
+ </pre>
47
+ Or:
48
+ <pre>
49
+ create_table :comments do |t|
50
+ t.references :article, :null => false
51
+ t.foreign_key :posts, :column => :article_id, :dependent => :nullify
52
+ end
53
+ </pre>
54
+ Which generates:
55
+ <pre>
56
+ CREATE TABLE "comments" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
57
+ "article_id" integer NOT NULL,
58
+ FOREIGN KEY ("article_id") REFERENCES "posts"(id) ON DELETE SET NULL);
59
+ </pre>
60
+
61
+ h2. Enforcing constraints
62
+
63
+ SQLite does not enforce database constraints out of the box
64
+ This provides you with the flexibility in choosing whether or not to enforce
65
+ constraints at the DB level or not.
66
+
67
+ In order to enforce your constraints:
68
+ <pre>
69
+ script/dbconsole
70
+ .genfkey --exec
71
+ </pre>
72
+
73
+ While your in the console run:
74
+ <pre>
75
+ .schema
76
+ </pre>
77
+ to see your constraints implemented as triggers
78
+
79
+ h2. schema.rb
80
+
81
+ All of the constrants are updated in schema.rb
82
+ when you run:
83
+ <pre>
84
+ rake db:migrate
85
+ rake db:schema:dump
86
+ </pre>
87
+ This allows you to see the state of your migratons and
88
+ take advantage of using <pre>rake db:schema:load</pre>
89
+
90
+ h2. Limitations
91
+
92
+ Since SQLite does not have complete ALTER TABLE support
93
+ you cannot use the following syntax:
94
+ <pre>
95
+ add_foreign_key
96
+ remove_foreign_key
97
+ </pre>
98
+
99
+ Therefore you must add your foreign keys when you define your table,
100
+ which may involve editing existing migration files instead of generating new ones
101
+
102
+ h2. Installation
103
+
104
+ Add the following to environment.rb:
105
+ <pre>
106
+ config.gem "dwilkie-foreigner", :lib => "foreigner", :source => "http://gemcutter.org"
107
+ </pre>
108
+ Then run
109
+ <pre>
110
+ sudo rake gems:install
111
+ </pre>
112
+
113
+ h2. See also
114
+
115
+ Don't need support for other databases?
116
+ Check out "sqlite-foreigner":http://github.com/dwilkie/sqlite-foreigner/tree/master
117
+
118
+ Copyright (c) 2009 David Wilkie, released under the MIT license
119
+
data/lib/foreigner.rb CHANGED
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
  end
15
15
 
16
16
  Base.class_eval do
17
- if %w(SQLite).include? connection_pool.spec.config[:adapter].downcase
17
+ if %w(sqlite3).include? connection_pool.spec.config[:adapter].downcase
18
18
  require "foreigner/connection_adapters/#{connection_pool.spec.config[:adapter].downcase}_adapter"
19
19
  end
20
20
  if %w(mysql postgresql).include? connection_pool.spec.config[:adapter].downcase
@@ -2,27 +2,28 @@ module Foreigner
2
2
  module ConnectionAdapters
3
3
  class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc:
4
4
  end
5
-
5
+
6
6
  module SchemaDefinitions
7
7
  def self.included(base)
8
8
  base::TableDefinition.class_eval do
9
9
  include Foreigner::ConnectionAdapters::TableDefinition
10
10
  end
11
-
11
+
12
12
  base::Table.class_eval do
13
13
  include Foreigner::ConnectionAdapters::Table
14
14
  end
15
15
  end
16
16
  end
17
-
17
+
18
18
  module TableDefinition
19
19
  def self.included(base)
20
20
  base.class_eval do
21
21
  include InstanceMethods
22
22
  alias_method_chain :references, :foreign_keys
23
+ alias_method_chain :to_sql, :foreign_keys
23
24
  end
24
25
  end
25
-
26
+
26
27
  module InstanceMethods
27
28
  def references_with_foreign_keys(*args)
28
29
  options = args.extract_options!
@@ -35,13 +36,24 @@ module Foreigner
35
36
 
36
37
  references_without_foreign_keys(*(args << options))
37
38
  end
38
-
39
+
39
40
  def foreign_key(to_table, options = {})
40
41
  ActiveSupport::Deprecation.warn(
41
42
  'adding a foreign key inside create_table is deprecated. ' +
42
43
  'to add a foreign key, use add_foreign_key', caller[0,10]
43
44
  )
44
45
  end
46
+
47
+ def to_sql_with_foreign_keys
48
+ sql = to_sql_without_foreign_keys
49
+ sql << ', ' << (foreign_keys * ', ') if foreign_keys.present?
50
+ sql
51
+ end
52
+
53
+ private
54
+ def foreign_keys
55
+ @foreign_keys ||= []
56
+ end
45
57
  end
46
58
  end
47
59
 
@@ -69,7 +81,7 @@ module Foreigner
69
81
  def foreign_key(to_table, options = {})
70
82
  @base.add_foreign_key(@table_name, to_table, options)
71
83
  end
72
-
84
+
73
85
  # Remove the given foreign key from the table.
74
86
  #
75
87
  # ===== Examples
@@ -82,17 +94,17 @@ module Foreigner
82
94
  def remove_foreign_key(options = {})
83
95
  @base.remove_foreign_key(@table_name, options)
84
96
  end
85
-
97
+
86
98
  # Adds a :foreign_key option to TableDefinition.references.
87
99
  # If :foreign_key is true, a foreign key constraint is added to the table.
88
100
  # You can also specify a hash, which is passed as foreign key options.
89
- #
101
+ #
90
102
  # ===== Examples
91
103
  # ====== Add goat_id column and a foreign key to the goats table.
92
104
  # t.references(:goat, :foreign_key => true)
93
105
  # ====== Add goat_id column and a cascading foreign key to the goats table.
94
106
  # t.references(:goat, :foreign_key => {:dependent => :delete})
95
- #
107
+ #
96
108
  # Note: No foreign key is created if :polymorphic => true is used.
97
109
  def references_with_foreign_keys(*args)
98
110
  options = args.extract_options!
@@ -110,3 +122,4 @@ module Foreigner
110
122
  end
111
123
  end
112
124
  end
125
+
@@ -4,7 +4,16 @@ module Foreigner
4
4
  def supports_foreign_keys?
5
5
  true
6
6
  end
7
-
7
+
8
+ def foreign_key_definition(to_table, options = {})
9
+ column = options[:column] || "#{to_table.to_s.singularize}_id"
10
+ dependency = dependency_sql(options[:dependent])
11
+
12
+ sql = "FOREIGN KEY (#{quote_column_name(column)}) REFERENCES #{quote_table_name(to_table)}(id)"
13
+ sql << " #{dependency}" unless dependency.blank?
14
+ sql
15
+ end
16
+
8
17
  def add_foreign_key(from_table, to_table, options = {})
9
18
  column = options[:column] || "#{to_table.to_s.singularize}_id"
10
19
  foreign_key_name = foreign_key_name(from_table, column, options)
@@ -18,7 +27,7 @@ module Foreigner
18
27
  "REFERENCES #{quote_table_name(ActiveRecord::Migrator.proper_table_name(to_table))}(#{primary_key})"
19
28
 
20
29
  sql << " #{dependency}" unless dependency.blank?
21
-
30
+
22
31
  execute(sql)
23
32
  end
24
33
 
@@ -40,4 +49,5 @@ module Foreigner
40
49
  end
41
50
  end
42
51
  end
43
- end
52
+ end
53
+
@@ -0,0 +1,104 @@
1
+ require 'foreigner/connection_adapters/sql_2003'
2
+
3
+ module Foreigner
4
+ module ConnectionAdapters
5
+ module TableDefinition
6
+ class ForeignKey < Struct.new(:base, :to_table, :options)
7
+
8
+ def to_sql
9
+ base.foreign_key_definition(to_table, options)
10
+ end
11
+ alias to_s :to_sql
12
+ end
13
+
14
+ def self.included(base)
15
+ base.class_eval do
16
+ include InstanceMethods
17
+ end
18
+ end
19
+
20
+ module InstanceMethods
21
+ # Adds a :foreign_key option to TableDefinition.references.
22
+ # If :foreign_key is true, a foreign key constraint is added to the table.
23
+ # You can also specify a hash, which is passed as foreign key options.
24
+ #
25
+ # ===== Examples
26
+ # ====== Add goat_id column and a foreign key to the goats table.
27
+ # t.references(:goat, :foreign_key => true)
28
+ # ====== Add goat_id column and a cascading foreign key to the goats table.
29
+ # t.references(:goat, :foreign_key => {:dependent => :delete})
30
+ #
31
+ # Note: No foreign key is created if :polymorphic => true is used.
32
+ def references_with_foreign_keys(*args)
33
+ options = args.extract_options!
34
+ fk_options = options.delete(:foreign_key)
35
+
36
+ if fk_options && !options[:polymorphic]
37
+ fk_options = {} if fk_options == true
38
+ args.each { |to_table| foreign_key(to_table, fk_options) }
39
+ end
40
+
41
+ references_without_foreign_keys(*(args << options))
42
+ end
43
+
44
+ # Defines a foreign key for the table. +to_table+ can be a single Symbol, or
45
+ # an Array of Symbols.
46
+ #
47
+ # ===== Examples
48
+ # ====== Creating a simple foreign key
49
+ # t.foreign_key(:people)
50
+ # ====== Defining the column
51
+ # t.foreign_key(:people, :column => :sender_id)
52
+ # ====== Specify cascading foreign key
53
+ # t.foreign_key(:people, :dependent => :delete)
54
+ def foreign_key(to_table, options = {})
55
+ if @base.supports_foreign_keys?
56
+ to_table = to_table.to_s.pluralize if ActiveRecord::Base.pluralize_table_names
57
+ foreign_keys << ForeignKey.new(@base, to_table, options)
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ module SQLite3Adapter
64
+ include Foreigner::ConnectionAdapters::Sql2003
65
+
66
+ def foreign_keys(table_name)
67
+ foreign_keys = []
68
+ create_table_info = select_value %{
69
+ SELECT sql
70
+ FROM sqlite_master
71
+ WHERE sql LIKE '%FOREIGN KEY%'
72
+ AND name = '#{table_name}'
73
+ }
74
+ unless create_table_info.nil?
75
+ fk_columns = create_table_info.scan(/FOREIGN KEY\s*\(\"([^\"]+)\"\)/)
76
+ fk_tables = create_table_info.scan(/REFERENCES\s*\"([^\"]+)\"/)
77
+ fk_references = create_table_info.scan(/REFERENCES[^\,]+/)
78
+ if fk_columns.size == fk_tables.size && fk_references.size == fk_columns.size
79
+ fk_columns.each_with_index do |fk_column, index|
80
+ if fk_references[index] =~ /ON DELETE CASCADE/
81
+ fk_references[index] = :delete
82
+ elsif fk_references[index] =~ /ON DELETE SET NULL/
83
+ fk_references[index] = :nullify
84
+ else
85
+ fk_references[index] = nil
86
+ end
87
+ foreign_keys << ForeignKeyDefinition.new(table_name, fk_tables[index][0], :column => fk_column[0], :dependent => fk_references[index])
88
+ end
89
+ end
90
+ end
91
+ foreign_keys
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ module ActiveRecord
98
+ module ConnectionAdapters
99
+ SQLite3Adapter.class_eval do
100
+ include Foreigner::ConnectionAdapters::SQLite3Adapter
101
+ end
102
+ end
103
+ end
104
+
@@ -28,13 +28,12 @@ module Foreigner
28
28
 
29
29
  private
30
30
  def foreign_keys(table_name, stream)
31
- debugger
32
31
  if (foreign_keys = @connection.foreign_keys(table_name)).any?
33
32
  add_foreign_key_statements = foreign_keys.map do |foreign_key|
34
33
  statement_parts = [ ('add_foreign_key ' + foreign_key.from_table.inspect) ]
35
34
  statement_parts << foreign_key.to_table.inspect
36
35
  statement_parts << (':name => ' + foreign_key.options[:name].inspect)
37
-
36
+
38
37
  if foreign_key.options[:column] != "#{foreign_key.to_table.singularize}_id"
39
38
  statement_parts << (':column => ' + foreign_key.options[:column].inspect)
40
39
  end
@@ -53,8 +52,9 @@ module Foreigner
53
52
  end
54
53
  end
55
54
 
56
- # This is a direct copy from
57
- # active_record/schema.dumper
55
+ # This is almost direct copy from
56
+ # active_record/schema.dumper with the add_foreign_keys method
57
+ # inserted into the middle
58
58
  def foreign_key_table(table, stream)
59
59
  columns = @connection.columns(table)
60
60
  begin
@@ -119,6 +119,7 @@ module Foreigner
119
119
  tbl.puts
120
120
  end
121
121
 
122
+ # add the foreign keys
122
123
  add_foreign_keys(table, tbl)
123
124
 
124
125
  tbl.puts " end"
@@ -142,6 +143,7 @@ module Foreigner
142
143
  add_foreign_key_statements = foreign_keys.map do |foreign_key|
143
144
  statement_parts = [" t.foreign_key " + foreign_key.to_table.inspect]
144
145
  statement_parts << (':column => ' + foreign_key.options[:column].inspect)
146
+ statement_parts << (':dependent => ' + foreign_key.options[:dependent].inspect)
145
147
  ' ' + statement_parts.join(', ')
146
148
  end
147
149
 
@@ -150,4 +152,5 @@ module Foreigner
150
152
  end
151
153
  end
152
154
  end
153
- end
155
+ end
156
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dwilkie-foreigner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Wilkie
@@ -20,17 +20,17 @@ executables: []
20
20
  extensions: []
21
21
 
22
22
  extra_rdoc_files:
23
- - README
23
+ - README.textile
24
24
  files:
25
25
  - MIT-LICENSE
26
26
  - Rakefile
27
- - README
27
+ - README.textile
28
28
  - lib/foreigner.rb
29
29
  - lib/foreigner/schema_dumper.rb
30
30
  - lib/foreigner/connection_adapters/sql_2003.rb
31
31
  - lib/foreigner/connection_adapters/mysql_adapter.rb
32
32
  - lib/foreigner/connection_adapters/postgresql_adapter.rb
33
- - lib/foreigner/connection_adapters/sqlite_adapter.rb
33
+ - lib/foreigner/connection_adapters/sqlite3_adapter.rb
34
34
  - lib/foreigner/connection_adapters/abstract/schema_definitions.rb
35
35
  - lib/foreigner/connection_adapters/abstract/schema_statements.rb
36
36
  - test/helper.rb
@@ -43,7 +43,7 @@ post_install_message:
43
43
  rdoc_options:
44
44
  - --line-numbers
45
45
  - --main
46
- - README
46
+ - README.textile
47
47
  require_paths:
48
48
  - lib
49
49
  required_ruby_version: !ruby/object:Gem::Requirement
data/README DELETED
@@ -1,76 +0,0 @@
1
- Foreigner
2
- =========
3
-
4
- Adds limited SQLite support for Matt Higgins Foreigner.
5
-
6
- Allows you to do the following in your migration files
7
- t.foreign_key :posts
8
-
9
- Which will generate the following SQL:
10
- FOREIGN KEY ("post_id") REFERENCES "posts"(id)
11
-
12
- To enforce the constraint use the genfkey tool included with SQLite:
13
- rake db:migrate
14
- sqlite3 db/development.sqlite3
15
- .genfkey --exec
16
-
17
- For more info see
18
- http://www.sqlite.org/cvstrac/fileview?f=sqlite/tool/genfkey.README
19
-
20
-
21
- Installation
22
- ------------
23
-
24
- Install as a plugin:
25
-
26
- ruby script/plugin install git://github.com/dwilkie/foreigner.git
27
-
28
- Install as a gem by adding the following to environment.rb:
29
-
30
- config.gem "dwilkie-foreigner", :lib => "foreigner", :source => "http://gemcutter.org"
31
-
32
-
33
- sudo rake gems:install
34
-
35
- API
36
- ---
37
- t.foreign_key(options)
38
- foreign_keys(table_name)
39
-
40
- Since SQLite does not have complete ALTER TABLE support
41
- you cannot use the following for an SQLite database
42
-
43
- add_foreign_key(from_table, to_table, options)
44
- remove_foreign_key(from_table, options)
45
-
46
-
47
- Example
48
- -------
49
-
50
- The most common use of foreign keys is to reference a table that a model belongs to.
51
- For example, given the following model:
52
-
53
- class Comment < ActiveRecord::Base
54
- belongs_to :post
55
- end
56
-
57
- class Post < ActiveRecord::Base
58
- has_many :comments, :dependent => :delete_all
59
- end
60
-
61
- You should add a foreign key in your migration:
62
-
63
- t.foreign_key :posts
64
-
65
- If the column is named article_id instead of post_id, use the :column option:
66
-
67
- t.foreign_key(:posts, :column => 'article_id')
68
-
69
- schema.rb
70
- ---------
71
-
72
- Similar to indexes, the foreign keys in your database are automatically dumped to schema.rb.
73
- This allows you to use foreign keys without fighting Rails!
74
-
75
- Copyright (c) 2009 David Wilkie, released under the MIT license
76
-
@@ -1,38 +0,0 @@
1
- require 'foreigner/connection_adapters/sql_2003'
2
-
3
- module Foreigner
4
- module ConnectionAdapters
5
- module SQLiteAdapter
6
- include Foreigner::ConnectionAdapters::Sql2003
7
-
8
- def foreign_keys(table_name)
9
- foreign_keys = []
10
- create_table_info = select_value %{
11
- SELECT sql
12
- FROM sqlite_master
13
- WHERE sql LIKE '%FOREIGN KEY%'
14
- AND name = '#{table_name}'
15
- }
16
- if !create_table_info.nil?
17
- fk_columns = create_table_info.scan(/FOREIGN KEY\s*\(\"([^\"]+)\"\)/)
18
- fk_tables = create_table_info.scan(/REFERENCES\s*\"([^\"]+)\"/)
19
- if fk_columns.size == fk_tables.size
20
- fk_columns.each_with_index do |fk_column, index|
21
- foreign_keys << ForeignKeyDefinition.new(table_name, fk_tables[index][0], :column => fk_column[0])
22
- end
23
- end
24
- end
25
- foreign_keys
26
- end
27
- end
28
- end
29
- end
30
-
31
- module ActiveRecord
32
- module ConnectionAdapters
33
- SQLiteAdapter.class_eval do
34
- include Foreigner::ConnectionAdapters::SQLiteAdapter
35
- end
36
- end
37
- end
38
-