hobofields 0.8.5 → 0.8.6
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/Rakefile +17 -2
- data/hobofields.gemspec +34 -134
- data/lib/hobo_fields/email_address.rb +1 -1
- data/lib/hobo_fields/field_spec.rb +14 -1
- data/lib/hobo_fields/migration_generator.rb +23 -21
- data/lib/hobo_fields/model_extensions.rb +3 -4
- data/lib/hobo_fields.rb +11 -5
- data/rails_generators/hobo_migration/hobo_migration_generator.rb +11 -10
- data/test/hobofields.rdoctest +22 -20
- data/test/hobofields_api.rdoctest +134 -113
- data/test/migration_generator.rdoctest +204 -174
- data/test/rich_types.rdoctest +134 -100
- data/test/test_hobofield_model_generator.rb +3 -2
- metadata +3 -3
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
# HoboFields - Migration Generator
|
|
2
2
|
|
|
3
3
|
Note that these doctests are good tests but not such good docs. The migration generator doesn't really fit well with the doctest concept of a single IRB session. As you'll see, there's a lot of jumping-through-hoops and doing stuff that no normal user of the migration generator would ever do.
|
|
4
|
+
{.hidden}
|
|
4
5
|
|
|
5
6
|
Firstly, in order to test the migration generator outside of a full Rails stack, there's a few things we need to do. First off we need to configure ActiveSupport for auto-loading
|
|
7
|
+
{.hidden}
|
|
6
8
|
|
|
7
9
|
>> require 'rubygems'
|
|
8
10
|
>> require 'activesupport'
|
|
9
|
-
>> Dependencies.load_paths << '.'
|
|
10
|
-
>> Dependencies.mechanism = :require
|
|
11
|
-
|
|
12
|
-
And we'll require:
|
|
13
|
-
|
|
14
11
|
>> require 'activerecord'
|
|
15
|
-
|
|
16
|
-
>> require 'hobofields'
|
|
12
|
+
{.hidden}
|
|
17
13
|
|
|
18
14
|
We also need to get ActiveRecord set up with a database connection
|
|
15
|
+
{.hidden}
|
|
19
16
|
|
|
20
17
|
>> mysql_database = "hobofields_doctest"
|
|
18
|
+
>> system "mysqladmin --force drop #{mysql_database} 2> /dev/null"
|
|
21
19
|
>> system("mysqladmin create #{mysql_database}") or raise "could not create database"
|
|
22
20
|
>> ActiveRecord::Base.establish_connection(:adapter => "mysql",
|
|
23
21
|
:database => mysql_database,
|
|
24
22
|
:host => "localhost")
|
|
23
|
+
{.hidden}
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
Some load path manipulation you shouldn't need:
|
|
26
|
+
{.hidden}
|
|
27
27
|
|
|
28
|
+
>> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobofields/lib')
|
|
29
|
+
>> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobosupport/lib')
|
|
30
|
+
{.hidden}
|
|
31
|
+
|
|
32
|
+
And we'll require:
|
|
33
|
+
{.hidden}
|
|
34
|
+
|
|
35
|
+
>> require 'hobosupport'
|
|
36
|
+
>> require 'hobofields'
|
|
28
37
|
|
|
29
38
|
## The migration generator -- introduction
|
|
30
39
|
|
|
@@ -62,17 +71,17 @@ The migration generator only takes into account classes that use HoboFields, i.e
|
|
|
62
71
|
Here we see a simple `create_table` migration along with the `drop_table` down migration
|
|
63
72
|
|
|
64
73
|
>>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
class Advert < ActiveRecord::Base
|
|
75
|
+
fields do
|
|
76
|
+
name :string
|
|
77
|
+
end
|
|
78
|
+
end
|
|
70
79
|
>> up, down = HoboFields::MigrationGenerator.run
|
|
71
80
|
>> up
|
|
72
|
-
=>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
=>
|
|
82
|
+
"create_table :adverts do |t|
|
|
83
|
+
t.string :name
|
|
84
|
+
end"
|
|
76
85
|
>> down
|
|
77
86
|
=> "drop_table :adverts"
|
|
78
87
|
|
|
@@ -85,13 +94,13 @@ Normally we would run the generated migration with `rake db:create`. We can achi
|
|
|
85
94
|
We'll define a method to make that easier next time
|
|
86
95
|
|
|
87
96
|
>>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
def migrate(renames={})
|
|
98
|
+
up, down = HoboFields::MigrationGenerator.run(renames)
|
|
99
|
+
puts up
|
|
100
|
+
ActiveRecord::Migration.class_eval(up)
|
|
101
|
+
ActiveRecord::Base.send(:subclasses).each { |model| model.reset_column_information }
|
|
102
|
+
[up, down]
|
|
103
|
+
end
|
|
95
104
|
|
|
96
105
|
We'll have a look at the migration generator in more detail later, first we'll have a look at the extra features HoboFields has added to the model.
|
|
97
106
|
|
|
@@ -101,22 +110,22 @@ We'll have a look at the migration generator in more detail later, first we'll h
|
|
|
101
110
|
If we add a new field to the model, the migration generator will add it to the database.
|
|
102
111
|
|
|
103
112
|
>>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
class Advert
|
|
114
|
+
fields do
|
|
115
|
+
name :string
|
|
116
|
+
body :text
|
|
117
|
+
published_at :datetime
|
|
118
|
+
end
|
|
119
|
+
end
|
|
111
120
|
>> up, down = migrate
|
|
112
121
|
>> up
|
|
113
|
-
=>
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
=>
|
|
123
|
+
"add_column :adverts, :body, :text
|
|
124
|
+
add_column :adverts, :published_at, :datetime"
|
|
116
125
|
>> down
|
|
117
|
-
=>
|
|
118
|
-
|
|
119
|
-
|
|
126
|
+
=>
|
|
127
|
+
"remove_column :adverts, :body
|
|
128
|
+
remove_column :adverts, :published_at"
|
|
120
129
|
>>
|
|
121
130
|
|
|
122
131
|
### Remove fields
|
|
@@ -124,12 +133,12 @@ If we add a new field to the model, the migration generator will add it to the d
|
|
|
124
133
|
If we remove a field from the model, the migration generator removes the database column. Note that we have to explicitly clear the known fields to achieve this in rdoctest -- in a Rails context you would simply edit the file
|
|
125
134
|
|
|
126
135
|
>> Advert.field_specs.clear # not normally needed
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
136
|
+
class Advert < ActiveRecord::Base
|
|
137
|
+
fields do
|
|
138
|
+
name :string
|
|
139
|
+
body :text
|
|
140
|
+
end
|
|
141
|
+
end
|
|
133
142
|
>> up, down = migrate
|
|
134
143
|
>> up
|
|
135
144
|
=> "remove_column :adverts, :published_at"
|
|
@@ -141,18 +150,18 @@ If we remove a field from the model, the migration generator removes the databas
|
|
|
141
150
|
Here we rename the `name` field to `title`. By default the generator sees this as removing `name` and adding `title`.
|
|
142
151
|
|
|
143
152
|
>> Advert.field_specs.clear # not normally needed
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
class Advert < ActiveRecord::Base
|
|
154
|
+
fields do
|
|
155
|
+
title :string
|
|
156
|
+
body :text
|
|
157
|
+
end
|
|
158
|
+
end
|
|
150
159
|
>> # Just generate - don't run the migration:
|
|
151
160
|
>> up, down = HoboFields::MigrationGenerator.run
|
|
152
161
|
>> up
|
|
153
|
-
=>
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
=>
|
|
163
|
+
"add_column :adverts, :title, :string
|
|
164
|
+
remove_column :adverts, :name"
|
|
156
165
|
>> down
|
|
157
166
|
=>""
|
|
158
167
|
remove_column :adverts, :title
|
|
@@ -176,13 +185,13 @@ Let's apply that change to the database
|
|
|
176
185
|
|
|
177
186
|
>> Advert.attr_type :title
|
|
178
187
|
=> String
|
|
179
|
-
>>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
188
|
+
>>
|
|
189
|
+
class Advert
|
|
190
|
+
fields do
|
|
191
|
+
title :text
|
|
192
|
+
body :text
|
|
193
|
+
end
|
|
194
|
+
end
|
|
186
195
|
>> up, down = HoboFields::MigrationGenerator.run
|
|
187
196
|
>> up
|
|
188
197
|
=> "change_column :adverts, :title, :text, :limit => nil"
|
|
@@ -193,15 +202,17 @@ Let's apply that change to the database
|
|
|
193
202
|
### Add a default
|
|
194
203
|
|
|
195
204
|
>>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
class Advert
|
|
206
|
+
fields do
|
|
207
|
+
title :string, :default => "Untitled"
|
|
208
|
+
body :text
|
|
209
|
+
end
|
|
210
|
+
end
|
|
202
211
|
>> up, down = migrate
|
|
203
|
-
>> up
|
|
204
|
-
=> 'change_column :adverts, :title, :string
|
|
212
|
+
>> up.split(',').slice(0,3).join(',')
|
|
213
|
+
=> 'change_column :adverts, :title, :string'
|
|
214
|
+
>> up.split(',').slice(3,2).sort.join(',')
|
|
215
|
+
=> ' :default => "Untitled", :limit => 255'
|
|
205
216
|
>> down
|
|
206
217
|
=> "change_column :adverts, :title, :string"
|
|
207
218
|
|
|
@@ -209,24 +220,23 @@ Let's apply that change to the database
|
|
|
209
220
|
### Limits
|
|
210
221
|
|
|
211
222
|
>>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
class Advert
|
|
224
|
+
fields do
|
|
225
|
+
price :integer, :limit => 2
|
|
226
|
+
end
|
|
227
|
+
end
|
|
217
228
|
>> up, down = HoboFields::MigrationGenerator.run
|
|
218
229
|
>> up
|
|
219
|
-
"add_column :
|
|
220
|
-
|
|
230
|
+
=> "add_column :adverts, :price, :integer, :limit => 2"
|
|
221
231
|
|
|
222
232
|
Note that limit on a decimal column is ignored (use :scale and :precision)
|
|
223
233
|
|
|
224
234
|
>>
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
235
|
+
class Advert
|
|
236
|
+
fields do
|
|
237
|
+
price :decimal, :limit => 4
|
|
238
|
+
end
|
|
239
|
+
end
|
|
230
240
|
>> up, down = HoboFields::MigrationGenerator.run
|
|
231
241
|
>> up
|
|
232
242
|
=> "add_column :adverts, :price, :decimal"
|
|
@@ -240,62 +250,62 @@ Cleanup
|
|
|
240
250
|
|
|
241
251
|
HoboFields extends the `belongs_to` macro so that it also declares the foreign-key field.
|
|
242
252
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
253
|
+
>>
|
|
254
|
+
class Advert
|
|
255
|
+
belongs_to :category
|
|
256
|
+
end
|
|
257
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
|
258
|
+
>> up
|
|
259
|
+
=> 'add_column :adverts, :category_id, :integer'
|
|
260
|
+
>> down
|
|
261
|
+
=> 'remove_column :adverts, :category_id'
|
|
252
262
|
|
|
253
263
|
Cleanup:
|
|
254
264
|
|
|
255
|
-
|
|
265
|
+
>> Advert.field_specs.delete(:category_id)
|
|
256
266
|
|
|
257
267
|
If you specify a custom foreign key, the migration generator observes that:
|
|
258
268
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
269
|
+
>>
|
|
270
|
+
class Advert
|
|
271
|
+
belongs_to :category, :foreign_key => "c_id"
|
|
272
|
+
end
|
|
273
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
|
274
|
+
>> up
|
|
275
|
+
=> 'add_column :adverts, :c_id, :integer'
|
|
276
|
+
>> down
|
|
277
|
+
=> 'remove_column :adverts, :c_id'
|
|
268
278
|
|
|
269
279
|
Cleanup:
|
|
270
280
|
|
|
271
|
-
|
|
281
|
+
>> Advert.field_specs.delete(:c_id)
|
|
272
282
|
|
|
273
283
|
|
|
274
284
|
### Timestamps
|
|
275
285
|
|
|
276
286
|
`updated_at` and `created_at` can be declared with the shorthand `timestamps`
|
|
277
287
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
288
|
+
>>
|
|
289
|
+
class Advert
|
|
290
|
+
fields do
|
|
291
|
+
timestamps
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
|
295
|
+
>> up
|
|
296
|
+
=>
|
|
297
|
+
"add_column :adverts, :created_at, :datetime
|
|
298
|
+
add_column :adverts, :updated_at, :datetime"
|
|
299
|
+
>> down
|
|
300
|
+
=>
|
|
301
|
+
"remove_column :adverts, :created_at
|
|
302
|
+
remove_column :adverts, :updated_at"
|
|
303
|
+
>>
|
|
294
304
|
|
|
295
305
|
Cleanup:
|
|
296
306
|
|
|
297
|
-
|
|
298
|
-
|
|
307
|
+
>> Advert.field_specs.delete(:updated_at)
|
|
308
|
+
>> Advert.field_specs.delete(:created_at)
|
|
299
309
|
|
|
300
310
|
|
|
301
311
|
### Rename a table
|
|
@@ -303,13 +313,13 @@ Cleanup:
|
|
|
303
313
|
The migration generator respects the `set_table_name` declaration, although as before, we need to explicitly tell the generator that we want a rename rather than a create and a drop.
|
|
304
314
|
|
|
305
315
|
>>
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
316
|
+
class Advert
|
|
317
|
+
set_table_name "ads"
|
|
318
|
+
fields do
|
|
319
|
+
title :string, :default => "Untitled"
|
|
320
|
+
body :text
|
|
321
|
+
end
|
|
322
|
+
end
|
|
313
323
|
>> up, down = HoboFields::MigrationGenerator.run(:adverts => :ads)
|
|
314
324
|
>> up
|
|
315
325
|
=> "rename_table :adverts, :ads"
|
|
@@ -334,25 +344,24 @@ Dropping tables is where the automatic down-migration really comes in handy:
|
|
|
334
344
|
>> up
|
|
335
345
|
=> "drop_table :adverts"
|
|
336
346
|
>> down
|
|
337
|
-
=>
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
>>
|
|
347
|
+
=>
|
|
348
|
+
"create_table "adverts", :force => true do |t|
|
|
349
|
+
t.text "body"
|
|
350
|
+
t.string "title", :default => "Untitled"
|
|
351
|
+
end"
|
|
343
352
|
|
|
344
353
|
### Rename a table
|
|
345
354
|
|
|
346
355
|
As with renaming columns, we have to tell the migration generator about the rename. Here we create a new class 'Advertisement'. Remember 'Advert' is being ignored so it's as if we renamed the definition in our models directory.
|
|
347
356
|
|
|
348
357
|
>>
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
358
|
+
class Advertisement < ActiveRecord::Base
|
|
359
|
+
fields do
|
|
360
|
+
title :string, :default => "Untitled"
|
|
361
|
+
body :text
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
>> up, down = HoboFields::MigrationGenerator.run(:adverts => :advertisements)
|
|
356
365
|
>> up
|
|
357
366
|
=> "rename_table :adverts, :advertisements"
|
|
358
367
|
>> down
|
|
@@ -366,20 +375,22 @@ Now that we've seen the renaming we'll switch the 'ignore' setting to ignore tha
|
|
|
366
375
|
|
|
367
376
|
### Adding an STI subclass
|
|
368
377
|
|
|
369
|
-
Adding a subclass should introduce the 'type' column and no other changes
|
|
378
|
+
Adding a subclass or two should introduce the 'type' column and no other changes
|
|
370
379
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
380
|
+
>>
|
|
381
|
+
class FancyAdvert < Advert
|
|
382
|
+
end
|
|
383
|
+
class SuperFancyAdvert < FancyAdvert
|
|
384
|
+
end
|
|
385
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
|
386
|
+
>> up
|
|
387
|
+
=> "add_column :adverts, :type, :string"
|
|
388
|
+
>> down
|
|
389
|
+
=> "remove_column :adverts, :type"
|
|
379
390
|
|
|
380
391
|
Cleanup
|
|
381
392
|
|
|
382
|
-
|
|
393
|
+
>> Advert.field_specs.delete(:type)
|
|
383
394
|
|
|
384
395
|
|
|
385
396
|
## Coping with multiple changes
|
|
@@ -400,43 +411,62 @@ First let's confirm we're in a known state. One model, 'Advert', with a string '
|
|
|
400
411
|
|
|
401
412
|
>> Advert.field_specs.clear
|
|
402
413
|
>>
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
414
|
+
class Advert
|
|
415
|
+
fields do
|
|
416
|
+
name :string, :default => "No Name"
|
|
417
|
+
body :text
|
|
418
|
+
end
|
|
419
|
+
end
|
|
409
420
|
>> up, down = HoboFields::MigrationGenerator.run(:adverts => {:title => :name})
|
|
410
421
|
>> up
|
|
411
|
-
=>
|
|
412
|
-
|
|
413
|
-
|
|
422
|
+
=>
|
|
423
|
+
"rename_column :adverts, :title, :name
|
|
424
|
+
change_column :adverts, :name, :string, :default => "No Name", :limit => 255"
|
|
414
425
|
>> down
|
|
415
|
-
=>
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
>>
|
|
426
|
+
=>
|
|
427
|
+
'rename_column :adverts, :name, :title
|
|
428
|
+
change_column :adverts, :title, :string, :default => "Untitled"'
|
|
419
429
|
|
|
420
430
|
|
|
421
431
|
### Rename a table and add a column
|
|
422
432
|
|
|
423
|
-
>> HoboFields::MigrationGenerator.ignore_models
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
433
|
+
>> HoboFields::MigrationGenerator.ignore_models += [:advert, :fancy_advert, :super_fancy_advert]
|
|
434
|
+
>>
|
|
435
|
+
class Ad < ActiveRecord::Base
|
|
436
|
+
fields do
|
|
437
|
+
title :string, :default => "Untitled"
|
|
438
|
+
body :text
|
|
439
|
+
created_at :datetime
|
|
440
|
+
end
|
|
441
|
+
end
|
|
431
442
|
>> up, down = HoboFields::MigrationGenerator.run(:adverts => :ads)
|
|
432
443
|
>> up
|
|
433
|
-
=>
|
|
434
|
-
|
|
444
|
+
=>
|
|
445
|
+
"rename_table :adverts, :ads
|
|
435
446
|
|
|
436
|
-
|
|
447
|
+
add_column :ads, :created_at, :datetime"
|
|
437
448
|
>>
|
|
438
|
-
|
|
439
449
|
|
|
440
|
-
##
|
|
450
|
+
## Legacy Keys
|
|
451
|
+
|
|
452
|
+
HoboFields has some support for legacy keys.
|
|
453
|
+
|
|
454
|
+
>> Advert.field_specs.clear
|
|
455
|
+
>>
|
|
456
|
+
class Advert
|
|
457
|
+
fields do
|
|
458
|
+
name :string, :default => "No Name"
|
|
459
|
+
body :text
|
|
460
|
+
end
|
|
461
|
+
set_primary_key "advert_id"
|
|
462
|
+
end
|
|
463
|
+
>> up, down = HoboFields::MigrationGenerator.run(:adverts => {:id => :advert_id})
|
|
464
|
+
>> up
|
|
465
|
+
=>
|
|
466
|
+
"rename_column :adverts, :id, :advert_id
|
|
467
|
+
|
|
468
|
+
Cleanup
|
|
469
|
+
{.hidden}
|
|
441
470
|
|
|
442
|
-
>> system "mysqladmin --force drop #{mysql_database}"
|
|
471
|
+
>> system "mysqladmin --force drop #{mysql_database} 2> /dev/null"
|
|
472
|
+
{.hidden}
|