Empact-activerecord-import 0.3.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/README.markdown +25 -0
- data/Rakefile +72 -0
- data/VERSION +1 -0
- data/lib/activerecord-import.rb +16 -0
- data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +10 -0
- data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +6 -0
- data/lib/activerecord-import/active_record/adapters/mysql_adapter.rb +6 -0
- data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +7 -0
- data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +7 -0
- data/lib/activerecord-import/adapters/abstract_adapter.rb +125 -0
- data/lib/activerecord-import/adapters/mysql_adapter.rb +50 -0
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +13 -0
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +7 -0
- data/lib/activerecord-import/base.rb +27 -0
- data/lib/activerecord-import/import.rb +347 -0
- data/lib/activerecord-import/mysql.rb +8 -0
- data/lib/activerecord-import/mysql2.rb +8 -0
- data/lib/activerecord-import/postgresql.rb +8 -0
- data/lib/activerecord-import/sqlite3.rb +8 -0
- data/test/active_record/connection_adapter_test.rb +52 -0
- data/test/adapters/mysql.rb +1 -0
- data/test/adapters/mysql2.rb +1 -0
- data/test/adapters/postgresql.rb +1 -0
- data/test/adapters/sqlite3.rb +1 -0
- data/test/import_test.rb +202 -0
- data/test/models/book.rb +3 -0
- data/test/models/group.rb +3 -0
- data/test/models/topic.rb +7 -0
- data/test/mysql/import_test.rb +6 -0
- data/test/mysql2/import_test.rb +6 -0
- data/test/postgresql/import_test.rb +20 -0
- data/test/schema/generic_schema.rb +98 -0
- data/test/schema/mysql_schema.rb +17 -0
- data/test/schema/version.rb +4 -0
- data/test/support/active_support/test_case_extensions.rb +67 -0
- data/test/support/factories.rb +13 -0
- data/test/support/generate.rb +29 -0
- data/test/support/mysql/assertions.rb +55 -0
- data/test/support/mysql/import_examples.rb +117 -0
- data/test/test_helper.rb +46 -0
- metadata +169 -0
@@ -0,0 +1,8 @@
|
|
1
|
+
warn <<-MSG
|
2
|
+
[DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
|
3
|
+
is deprecated. Update to autorequire using 'require "activerecord-import"'. See
|
4
|
+
http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
|
5
|
+
MSG
|
6
|
+
|
7
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
|
8
|
+
|
@@ -0,0 +1,8 @@
|
|
1
|
+
warn <<-MSG
|
2
|
+
[DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
|
3
|
+
is deprecated. Update to autorequire using 'require "activerecord-import"'. See
|
4
|
+
http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
|
5
|
+
MSG
|
6
|
+
|
7
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
|
8
|
+
|
@@ -0,0 +1,8 @@
|
|
1
|
+
warn <<-MSG
|
2
|
+
[DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
|
3
|
+
is deprecated. Update to autorequire using 'require "activerecord-import"'. See
|
4
|
+
http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
|
5
|
+
MSG
|
6
|
+
|
7
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
|
8
|
+
|
@@ -0,0 +1,8 @@
|
|
1
|
+
warn <<-MSG
|
2
|
+
[DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
|
3
|
+
is deprecated. Update to autorequire using 'require "activerecord-import"'. See
|
4
|
+
http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
|
5
|
+
MSG
|
6
|
+
|
7
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
|
8
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
describe "ActiveRecord::ConnectionAdapter::AbstractAdapter" do
|
4
|
+
context "#get_insert_value_sets - computing insert value sets" do
|
5
|
+
let(:adapter){ ActiveRecord::ConnectionAdapters::AbstractAdapter }
|
6
|
+
let(:base_sql){ "INSERT INTO atable (a,b,c)" }
|
7
|
+
let(:values){ [ "(1,2,3)", "(2,3,4)", "(3,4,5)" ] }
|
8
|
+
|
9
|
+
context "when the max allowed bytes is 33 and the base SQL is 26 bytes" do
|
10
|
+
it "should return 3 value sets when given 3 value sets of 7 bytes a piece" do
|
11
|
+
value_sets = adapter.get_insert_value_sets values, base_sql.size, max_allowed_bytes = 33
|
12
|
+
assert_equal 3, value_sets.size
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when the max allowed bytes is 40 and the base SQL is 26 bytes" do
|
17
|
+
it "should return 3 value sets when given 3 value sets of 7 bytes a piece" do
|
18
|
+
value_sets = adapter.get_insert_value_sets values, base_sql.size, max_allowed_bytes = 40
|
19
|
+
assert_equal 3, value_sets.size
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when the max allowed bytes is 41 and the base SQL is 26 bytes" do
|
24
|
+
it "should return 2 value sets when given 2 value sets of 7 bytes a piece" do
|
25
|
+
value_sets = adapter.get_insert_value_sets values, base_sql.size, max_allowed_bytes = 41
|
26
|
+
assert_equal 2, value_sets.size
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when the max allowed bytes is 48 and the base SQL is 26 bytes" do
|
31
|
+
it "should return 2 value sets when given 2 value sets of 7 bytes a piece" do
|
32
|
+
value_sets = adapter.get_insert_value_sets values, base_sql.size, max_allowed_bytes = 48
|
33
|
+
assert_equal 2, value_sets.size
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when the max allowed bytes is 49 and the base SQL is 26 bytes" do
|
38
|
+
it "should return 1 value sets when given 1 value sets of 7 bytes a piece" do
|
39
|
+
value_sets = adapter.get_insert_value_sets values, base_sql.size, max_allowed_bytes = 49
|
40
|
+
assert_equal 1, value_sets.size
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when the max allowed bytes is 999999 and the base SQL is 26 bytes" do
|
45
|
+
it "should return 1 value sets when given 1 value sets of 7 bytes a piece" do
|
46
|
+
value_sets = adapter.get_insert_value_sets values, base_sql.size, max_allowed_bytes = 999999
|
47
|
+
assert_equal 1, value_sets.size
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
ENV["ARE_DB"] = "mysql"
|
@@ -0,0 +1 @@
|
|
1
|
+
ENV["ARE_DB"] = "mysql2"
|
@@ -0,0 +1 @@
|
|
1
|
+
ENV["ARE_DB"] = "postgresql"
|
@@ -0,0 +1 @@
|
|
1
|
+
ENV["ARE_DB"] = "sqlite3"
|
data/test/import_test.rb
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "#import" do
|
4
|
+
it "should return the number of inserts performed" do
|
5
|
+
# see ActiveRecord::ConnectionAdapters::AbstractAdapter test for more specifics
|
6
|
+
assert_difference "Topic.count", +10 do
|
7
|
+
result = Topic.import Build(3, :topics)
|
8
|
+
assert result.num_inserts > 0
|
9
|
+
|
10
|
+
result = Topic.import Build(7, :topics)
|
11
|
+
assert result.num_inserts > 0
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "with :validation option" do
|
16
|
+
let(:columns) { %w(title author_name) }
|
17
|
+
let(:valid_values) { [[ "LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
|
18
|
+
let(:invalid_values) { [[ "The RSpec Book", ""], ["Agile+UX", ""]] }
|
19
|
+
|
20
|
+
context "with validation checks turned off" do
|
21
|
+
it "should import valid data" do
|
22
|
+
assert_difference "Topic.count", +2 do
|
23
|
+
result = Topic.import columns, valid_values, :validate => false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should import invalid data" do
|
28
|
+
assert_difference "Topic.count", +2 do
|
29
|
+
result = Topic.import columns, invalid_values, :validate => false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with validation checks turned on" do
|
35
|
+
it "should import valid data" do
|
36
|
+
assert_difference "Topic.count", +2 do
|
37
|
+
result = Topic.import columns, valid_values, :validate => true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should not import invalid data" do
|
42
|
+
assert_no_difference "Topic.count" do
|
43
|
+
result = Topic.import columns, invalid_values, :validate => true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should report the failed instances" do
|
48
|
+
results = Topic.import columns, invalid_values, :validate => true
|
49
|
+
assert_equal invalid_values.size, results.failed_instances.size
|
50
|
+
results.failed_instances.each{ |e| assert_kind_of Topic, e }
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should import valid data when mixed with invalid data" do
|
54
|
+
assert_difference "Topic.count", +2 do
|
55
|
+
result = Topic.import columns, valid_values + invalid_values, :validate => true
|
56
|
+
end
|
57
|
+
assert_equal 0, Topic.find_all_by_title(invalid_values.map(&:first)).count
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "with an array of unsaved model instances" do
|
63
|
+
let(:topic) { Build(:topic, :title => "The RSpec Book", :author_name => "David Chelimsky")}
|
64
|
+
let(:topics) { Build(9, :topics) }
|
65
|
+
let(:invalid_topics){ Build(7, :invalid_topics)}
|
66
|
+
|
67
|
+
it "should import records based on those model's attributes" do
|
68
|
+
assert_difference "Topic.count", +9 do
|
69
|
+
result = Topic.import topics
|
70
|
+
end
|
71
|
+
|
72
|
+
Topic.import [topic]
|
73
|
+
assert Topic.find_by_title_and_author_name("The RSpec Book", "David Chelimsky")
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should not overwrite existing records" do
|
77
|
+
topic = Generate(:topic, :title => "foobar")
|
78
|
+
assert_no_difference "Topic.count" do
|
79
|
+
begin
|
80
|
+
Topic.transaction do
|
81
|
+
topic.title = "baz"
|
82
|
+
Topic.import [topic]
|
83
|
+
end
|
84
|
+
rescue Exception
|
85
|
+
# PostgreSQL raises PgError due to key constraints
|
86
|
+
# I don't know why ActiveRecord doesn't catch these. *sigh*
|
87
|
+
end
|
88
|
+
end
|
89
|
+
assert_equal "foobar", topic.reload.title
|
90
|
+
end
|
91
|
+
|
92
|
+
context "with validation checks turned on" do
|
93
|
+
it "should import valid models" do
|
94
|
+
assert_difference "Topic.count", +9 do
|
95
|
+
result = Topic.import topics, :validate => true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should not import invalid models" do
|
100
|
+
assert_no_difference "Topic.count" do
|
101
|
+
result = Topic.import invalid_topics, :validate => true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "with validation checks turned off" do
|
107
|
+
it "should import invalid models" do
|
108
|
+
assert_difference "Topic.count", +7 do
|
109
|
+
result = Topic.import invalid_topics, :validate => false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "with an array of columns and an array of unsaved model instances" do
|
116
|
+
let(:topics) { Build(2, :topics) }
|
117
|
+
|
118
|
+
it "should import records populating the supplied columns with the corresponding model instance attributes" do
|
119
|
+
assert_difference "Topic.count", +2 do
|
120
|
+
result = Topic.import [:author_name, :title], topics
|
121
|
+
end
|
122
|
+
|
123
|
+
# imported topics should be findable by their imported attributes
|
124
|
+
assert Topic.find_by_author_name(topics.first.author_name)
|
125
|
+
assert Topic.find_by_author_name(topics.last.author_name)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should not populate fields for columns not imported" do
|
129
|
+
topics.first.author_email_address = "zach.dennis@gmail.com"
|
130
|
+
assert_difference "Topic.count", +2 do
|
131
|
+
result = Topic.import [:author_name, :title], topics
|
132
|
+
end
|
133
|
+
|
134
|
+
assert !Topic.find_by_author_email_address("zach.dennis@gmail.com")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "ActiveRecord timestamps" do
|
139
|
+
context "when the timestamps columns are present" do
|
140
|
+
setup do
|
141
|
+
Delorean.time_travel_to("5 minutes ago") do
|
142
|
+
assert_difference "Book.count", +1 do
|
143
|
+
result = Book.import [:title, :author_name, :publisher], [["LDAP", "Big Bird", "Del Rey"]]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
@book = Book.last
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should set the created_at column for new records" do
|
150
|
+
assert_equal 5.minutes.ago.strftime("%H:%M"), @book.created_at.strftime("%H:%M")
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should set the created_on column for new records" do
|
154
|
+
assert_equal 5.minutes.ago.strftime("%H:%M"), @book.created_on.strftime("%H:%M")
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should set the updated_at column for new records" do
|
158
|
+
assert_equal 5.minutes.ago.strftime("%H:%M"), @book.updated_at.strftime("%H:%M")
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should set the updated_on column for new records" do
|
162
|
+
assert_equal 5.minutes.ago.strftime("%H:%M"), @book.updated_on.strftime("%H:%M")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context "when a custom time zone is set" do
|
167
|
+
setup do
|
168
|
+
original_timezone = ActiveRecord::Base.default_timezone
|
169
|
+
ActiveRecord::Base.default_timezone = :utc
|
170
|
+
Delorean.time_travel_to("5 minutes ago") do
|
171
|
+
assert_difference "Book.count", +1 do
|
172
|
+
result = Book.import [:title, :author_name, :publisher], [["LDAP", "Big Bird", "Del Rey"]]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
ActiveRecord::Base.default_timezone = original_timezone
|
176
|
+
@book = Book.last
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should set the created_at and created_on timestamps for new records" do
|
180
|
+
assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @book.created_at.strftime("%H:%M")
|
181
|
+
assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @book.created_on.strftime("%H:%M")
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should set the updated_at and updated_on timestamps for new records" do
|
185
|
+
assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @book.updated_at.strftime("%H:%M")
|
186
|
+
assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @book.updated_on.strftime("%H:%M")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context "importing with database reserved words" do
|
192
|
+
let(:group) { Build(:group, :order => "superx") }
|
193
|
+
|
194
|
+
it "should import just fine" do
|
195
|
+
assert_difference "Group.count", +1 do
|
196
|
+
result = Group.import [group]
|
197
|
+
end
|
198
|
+
assert_equal "superx", Group.first.order
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
data/test/models/book.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
class Topic < ActiveRecord::Base
|
2
|
+
validates_presence_of :author_name
|
3
|
+
has_many :books
|
4
|
+
belongs_to :parent, :class_name => "Topic"
|
5
|
+
|
6
|
+
composed_of :description, :mapping => [ %w(title title), %w(author_name author_name)], :allow_nil => true, :class_name => "TopicDescription"
|
7
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/assertions')
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
|
5
|
+
|
6
|
+
should_support_mysql_import_functionality
|
@@ -0,0 +1,6 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/assertions')
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
|
5
|
+
|
6
|
+
should_support_mysql_import_functionality
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "#supports_imports?" do
|
4
|
+
it "should support import" do
|
5
|
+
assert ActiveRecord::Base.supports_import?
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#import" do
|
10
|
+
it "should import with a single insert" do
|
11
|
+
# see ActiveRecord::ConnectionAdapters::AbstractAdapter test for more specifics
|
12
|
+
assert_difference "Topic.count", +10 do
|
13
|
+
result = Topic.import Build(3, :topics)
|
14
|
+
assert_equal 1, result.num_inserts
|
15
|
+
|
16
|
+
result = Topic.import Build(7, :topics)
|
17
|
+
assert_equal 1, result.num_inserts
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
|
3
|
+
create_table :schema_info, :force=>true do |t|
|
4
|
+
t.column :version, :integer, :unique=>true
|
5
|
+
end
|
6
|
+
SchemaInfo.create :version=>SchemaInfo::VERSION
|
7
|
+
|
8
|
+
create_table :group, :force => true do |t|
|
9
|
+
t.column :order, :string
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
|
13
|
+
create_table :topics, :force=>true do |t|
|
14
|
+
t.column :title, :string, :null => false
|
15
|
+
t.column :author_name, :string
|
16
|
+
t.column :author_email_address, :string
|
17
|
+
t.column :written_on, :datetime
|
18
|
+
t.column :bonus_time, :time
|
19
|
+
t.column :last_read, :datetime
|
20
|
+
t.column :content, :text
|
21
|
+
t.column :approved, :boolean, :default=>'1'
|
22
|
+
t.column :replies_count, :integer
|
23
|
+
t.column :parent_id, :integer
|
24
|
+
t.column :type, :string
|
25
|
+
t.column :created_at, :datetime
|
26
|
+
t.column :created_on, :datetime
|
27
|
+
t.column :updated_at, :datetime
|
28
|
+
t.column :updated_on, :datetime
|
29
|
+
end
|
30
|
+
|
31
|
+
create_table :projects, :force=>true do |t|
|
32
|
+
t.column :name, :string
|
33
|
+
t.column :type, :string
|
34
|
+
end
|
35
|
+
|
36
|
+
create_table :developers, :force=>true do |t|
|
37
|
+
t.column :name, :string
|
38
|
+
t.column :salary, :integer, :default=>'70000'
|
39
|
+
t.column :created_at, :datetime
|
40
|
+
t.column :team_id, :integer
|
41
|
+
t.column :updated_at, :datetime
|
42
|
+
end
|
43
|
+
|
44
|
+
create_table :addresses, :force=>true do |t|
|
45
|
+
t.column :address, :string
|
46
|
+
t.column :city, :string
|
47
|
+
t.column :state, :string
|
48
|
+
t.column :zip, :string
|
49
|
+
t.column :developer_id, :integer
|
50
|
+
end
|
51
|
+
|
52
|
+
create_table :teams, :force=>true do |t|
|
53
|
+
t.column :name, :string
|
54
|
+
end
|
55
|
+
|
56
|
+
create_table :books, :force=>true do |t|
|
57
|
+
t.column :title, :string, :null=>false
|
58
|
+
t.column :publisher, :string, :null=>false, :default => 'Default Publisher'
|
59
|
+
t.column :author_name, :string, :null=>false
|
60
|
+
t.column :created_at, :datetime
|
61
|
+
t.column :created_on, :datetime
|
62
|
+
t.column :updated_at, :datetime
|
63
|
+
t.column :updated_on, :datetime
|
64
|
+
t.column :publish_date, :date
|
65
|
+
t.column :topic_id, :integer
|
66
|
+
t.column :for_sale, :boolean, :default => true
|
67
|
+
end
|
68
|
+
|
69
|
+
create_table :languages, :force=>true do |t|
|
70
|
+
t.column :name, :string
|
71
|
+
t.column :developer_id, :integer
|
72
|
+
end
|
73
|
+
|
74
|
+
create_table :shopping_carts, :force=>true do |t|
|
75
|
+
t.column :name, :string, :null => true
|
76
|
+
t.column :created_at, :datetime
|
77
|
+
t.column :updated_at, :datetime
|
78
|
+
end
|
79
|
+
|
80
|
+
create_table :cart_items, :force => true do |t|
|
81
|
+
t.column :shopping_cart_id, :string, :null => false
|
82
|
+
t.column :book_id, :string, :null => false
|
83
|
+
t.column :copies, :integer, :default => 1
|
84
|
+
t.column :created_at, :datetime
|
85
|
+
t.column :updated_at, :datetime
|
86
|
+
end
|
87
|
+
|
88
|
+
add_index :cart_items, [:shopping_cart_id, :book_id], :unique => true, :name => 'uk_shopping_cart_books'
|
89
|
+
|
90
|
+
create_table :animals, :force => true do |t|
|
91
|
+
t.column :name, :string, :null => false
|
92
|
+
t.column :size, :string, :default => nil
|
93
|
+
t.column :created_at, :datetime
|
94
|
+
t.column :updated_at, :datetime
|
95
|
+
end
|
96
|
+
|
97
|
+
add_index :animals, [:name], :unique => true, :name => 'uk_animals'
|
98
|
+
end
|