hobo_fields 1.3.0.RC

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGES.txt +38 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.txt +8 -0
  4. data/Rakefile +36 -0
  5. data/VERSION +1 -0
  6. data/bin/hobofields +19 -0
  7. data/hobo_fields.gemspec +31 -0
  8. data/lib/generators/hobo/migration/USAGE +47 -0
  9. data/lib/generators/hobo/migration/migration_generator.rb +162 -0
  10. data/lib/generators/hobo/migration/migrator.rb +445 -0
  11. data/lib/generators/hobo/migration/templates/migration.rb.erb +9 -0
  12. data/lib/generators/hobo/model/USAGE +19 -0
  13. data/lib/generators/hobo/model/model_generator.rb +11 -0
  14. data/lib/generators/hobo/model/templates/model_injection.rb.erb +18 -0
  15. data/lib/hobo_fields/extensions/active_record/attribute_methods.rb +48 -0
  16. data/lib/hobo_fields/extensions/active_record/fields_declaration.rb +21 -0
  17. data/lib/hobo_fields/field_declaration_dsl.rb +33 -0
  18. data/lib/hobo_fields/model/field_spec.rb +121 -0
  19. data/lib/hobo_fields/model/index_spec.rb +47 -0
  20. data/lib/hobo_fields/model.rb +226 -0
  21. data/lib/hobo_fields/railtie.rb +13 -0
  22. data/lib/hobo_fields/sanitize_html.rb +23 -0
  23. data/lib/hobo_fields/types/email_address.rb +26 -0
  24. data/lib/hobo_fields/types/enum_string.rb +101 -0
  25. data/lib/hobo_fields/types/html_string.rb +15 -0
  26. data/lib/hobo_fields/types/lifecycle_state.rb +16 -0
  27. data/lib/hobo_fields/types/markdown_string.rb +15 -0
  28. data/lib/hobo_fields/types/password_string.rb +15 -0
  29. data/lib/hobo_fields/types/raw_html_string.rb +13 -0
  30. data/lib/hobo_fields/types/raw_markdown_string.rb +13 -0
  31. data/lib/hobo_fields/types/serialized_object.rb +15 -0
  32. data/lib/hobo_fields/types/text.rb +16 -0
  33. data/lib/hobo_fields/types/textile_string.rb +22 -0
  34. data/lib/hobo_fields.rb +94 -0
  35. data/test/api.rdoctest +244 -0
  36. data/test/doc-only.rdoctest +96 -0
  37. data/test/generators.rdoctest +53 -0
  38. data/test/interactive_primary_key.rdoctest +54 -0
  39. data/test/migration_generator.rdoctest +639 -0
  40. data/test/migration_generator_comments.rdoctest +75 -0
  41. data/test/prepare_testapp.rb +8 -0
  42. data/test/rich_types.rdoctest +394 -0
  43. metadata +140 -0
@@ -0,0 +1,639 @@
1
+ # HoboFields - Migration Generator
2
+
3
+ Our test requires to prepare the testapp:
4
+ {.hidden}
5
+
6
+ doctest_require: 'prepare_testapp'
7
+
8
+ {.hidden}
9
+
10
+ ## The migration generator -- introduction
11
+
12
+ The migration generator works by:
13
+
14
+ * Loading all of the models in your Rails app
15
+ * Using the Rails schema-dumper to extract information about the current state of the database.
16
+ * Calculating the changes that are required to bring the database into sync with your application.
17
+
18
+ Normally you would run the migration generator as a regular Rails generator. You would type
19
+
20
+ $ script/generator hobo_migration
21
+
22
+ in your Rails app, and the migration file would be created in `db/migrate`.
23
+
24
+ In order to demonstrate the generator in this doctest script however, we'll be using the Ruby API instead. The method `Generators::Hobo::Migration::Migrator.run` returns a pair of strings -- the up migration and the down migration.
25
+
26
+ At the moment the database is empty and no ActiveRecord models exist, so the generator is going to tell us there is nothing to do.
27
+
28
+ >> Generators::Hobo::Migration::Migrator.run
29
+ => ["", ""]
30
+
31
+
32
+ ### Models without `fields do` are ignored
33
+
34
+ The migration generator only takes into account classes that use HoboFields, i.e. classes with a `fields do` declaration. Models without this are ignored:
35
+
36
+ >> class Advert < ActiveRecord::Base; end
37
+ >> Generators::Hobo::Migration::Migrator.run
38
+ => ["", ""]
39
+
40
+ You can also tell HoboFields to ignore additional tables. You can place this command in your environment.rb or elsewhere:
41
+
42
+ >> Generators::Hobo::Migration::Migrator.ignore_tables = ["green_fishes"]
43
+
44
+ ### Create the table
45
+
46
+ Here we see a simple `create_table` migration along with the `drop_table` down migration
47
+
48
+ >>
49
+ class Advert < ActiveRecord::Base
50
+ fields do
51
+ name :string
52
+ end
53
+ end
54
+ >> up, down = Generators::Hobo::Migration::Migrator.run
55
+ >> up
56
+ =>
57
+ "create_table :adverts do |t|
58
+ t.string :name
59
+ end"
60
+ >> down
61
+ => "drop_table :adverts"
62
+
63
+ Normally we would run the generated migration with `rake db:create`. We can achieve the same effect directly in Ruby like this:
64
+
65
+ >> ActiveRecord::Migration.class_eval up
66
+ >> Advert.columns.*.name
67
+ => ["id", "name"]
68
+
69
+ We'll define a method to make that easier next time
70
+
71
+ >>
72
+ def migrate(renames={})
73
+ up, down = Generators::Hobo::Migration::Migrator.run(renames)
74
+ ActiveRecord::Migration.class_eval(up)
75
+ ActiveRecord::Base.send(:descendants).each { |model| model.reset_column_information }
76
+ [up, down]
77
+ end
78
+
79
+ 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.
80
+
81
+
82
+ ### Add fields
83
+
84
+ If we add a new field to the model, the migration generator will add it to the database.
85
+
86
+ >>
87
+ class Advert
88
+ fields do
89
+ name :string
90
+ body :text
91
+ published_at :datetime
92
+ end
93
+ end
94
+ >> up, down = migrate
95
+ >> up
96
+ =>
97
+ "add_column :adverts, :body, :text
98
+ add_column :adverts, :published_at, :datetime"
99
+ >> down
100
+ =>
101
+ "remove_column :adverts, :body
102
+ remove_column :adverts, :published_at"
103
+ >>
104
+
105
+ ### Remove fields
106
+
107
+ 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
108
+
109
+ >> Advert.field_specs.clear # not normally needed
110
+ class Advert < ActiveRecord::Base
111
+ fields do
112
+ name :string
113
+ body :text
114
+ end
115
+ end
116
+ >> up, down = migrate
117
+ >> up
118
+ => "remove_column :adverts, :published_at"
119
+ >> down
120
+ => "add_column :adverts, :published_at, :datetime"
121
+
122
+ ### Rename a field
123
+
124
+ Here we rename the `name` field to `title`. By default the generator sees this as removing `name` and adding `title`.
125
+
126
+ >> Advert.field_specs.clear # not normally needed
127
+ class Advert < ActiveRecord::Base
128
+ fields do
129
+ title :string
130
+ body :text
131
+ end
132
+ end
133
+ >> # Just generate - don't run the migration:
134
+ >> up, down = Generators::Hobo::Migration::Migrator.run
135
+ >> up
136
+ =>
137
+ "add_column :adverts, :title, :string
138
+ remove_column :adverts, :name"
139
+ >> down
140
+ =>""
141
+ remove_column :adverts, :title
142
+ add_column :adverts, :name, :string
143
+ >>
144
+
145
+ When run as a generator, the migration-generator won't make this assumption. Instead it will prompt for user input to resolve the ambiguity. When using the Ruby API, we can ask for a rename instead of an add + drop by passing in a hash:
146
+
147
+ >> up, down = Generators::Hobo::Migration::Migrator.run(:adverts => { :name => :title })
148
+ >> up
149
+ => "rename_column :adverts, :name, :title"
150
+ >> down
151
+ => "rename_column :adverts, :title, :name"
152
+
153
+ Let's apply that change to the database
154
+
155
+ >> migrate
156
+
157
+
158
+ ### Change a type
159
+
160
+ >> Advert.attr_type :title
161
+ => String
162
+ >>
163
+ class Advert
164
+ fields do
165
+ title :text
166
+ body :text
167
+ end
168
+ end
169
+ >> up, down = Generators::Hobo::Migration::Migrator.run
170
+ >> up
171
+ => "change_column :adverts, :title, :text, :limit => nil"
172
+ >> down
173
+ => "change_column :adverts, :title, :string"
174
+
175
+
176
+ ### Add a default
177
+
178
+ >>
179
+ class Advert
180
+ fields do
181
+ title :string, :default => "Untitled"
182
+ body :text
183
+ end
184
+ end
185
+ >> up, down = migrate
186
+ >> up.split(',').slice(0,3).join(',')
187
+ => 'change_column :adverts, :title, :string'
188
+ >> up.split(',').slice(3,2).sort.join(',')
189
+ => ' :default => "Untitled", :limit => 255'
190
+ >> down
191
+ => "change_column :adverts, :title, :string"
192
+
193
+
194
+ ### Limits
195
+
196
+ >>
197
+ class Advert
198
+ fields do
199
+ price :integer, :limit => 2
200
+ end
201
+ end
202
+ >> up, down = Generators::Hobo::Migration::Migrator.run
203
+ >> up
204
+ => "add_column :adverts, :price, :integer, :limit => 2"
205
+
206
+ Note that limit on a decimal column is ignored (use :scale and :precision)
207
+
208
+ >>
209
+ class Advert
210
+ fields do
211
+ price :decimal, :limit => 4
212
+ end
213
+ end
214
+ >> up, down = Generators::Hobo::Migration::Migrator.run
215
+ >> up
216
+ => "add_column :adverts, :price, :decimal"
217
+
218
+ Cleanup
219
+ {.hidden}
220
+
221
+ >> Advert.field_specs.delete :price
222
+ {.hidden}
223
+
224
+
225
+ ### Foreign Keys
226
+
227
+ HoboFields extends the `belongs_to` macro so that it also declares the
228
+ foreign-key field. It also generates an index on the field.
229
+
230
+ >>
231
+ class Advert
232
+ belongs_to :category
233
+ end
234
+ >> up, down = Generators::Hobo::Migration::Migrator.run
235
+ >> up
236
+ =>
237
+ "add_column :adverts, :category_id, :integer
238
+
239
+ add_index :adverts, [:category_id]"
240
+ >> down
241
+ =>
242
+ "remove_column :adverts, :category_id
243
+
244
+ remove_index :adverts, :name => :index_adverts_on_category_id rescue ActiveRecord::StatementInvalid"
245
+
246
+ Cleanup:
247
+ {.hidden}
248
+
249
+ >> Advert.field_specs.delete(:category_id)
250
+ >> Advert.index_specs.delete_if {|spec| spec.fields==["category_id"]}
251
+ {.hidden}
252
+
253
+ If you specify a custom foreign key, the migration generator observes that:
254
+
255
+ >>
256
+ class Advert
257
+ belongs_to :category, :foreign_key => "c_id"
258
+ end
259
+ >> up, down = Generators::Hobo::Migration::Migrator.run
260
+ >> up
261
+ =>
262
+ "add_column :adverts, :c_id, :integer
263
+
264
+ add_index :adverts, [:c_id]"
265
+
266
+ Cleanup:
267
+ {.hidden}
268
+
269
+ >> Advert.field_specs.delete(:c_id)
270
+ >> Advert.index_specs.delete_if {|spec| spec.fields==["c_id"]}
271
+ {.hidden}
272
+
273
+ You can avoid generating the index by specifying `:index => false`
274
+
275
+ >>
276
+ class Advert
277
+ belongs_to :category, :index => false
278
+ end
279
+ >> up, down = Generators::Hobo::Migration::Migrator.run
280
+ >> up
281
+ => "add_column :adverts, :category_id, :integer"
282
+
283
+ Cleanup:
284
+ {.hidden}
285
+
286
+ >> Advert.field_specs.delete(:category_id)
287
+ >> Advert.index_specs.delete_if {|spec| spec.fields==["category_id"]}
288
+ {.hidden}
289
+
290
+ You can specify the index name with :index
291
+
292
+ >>
293
+ class Advert
294
+ belongs_to :category, :index => 'my_index'
295
+ end
296
+ >> up, down = Generators::Hobo::Migration::Migrator.run
297
+ >> up
298
+ =>
299
+ "add_column :adverts, :category_id, :integer
300
+
301
+ add_index :adverts, [:category_id], :name => 'my_index'"
302
+
303
+ Cleanup:
304
+ {.hidden}
305
+
306
+ >> Advert.field_specs.delete(:category_id)
307
+ >> Advert.index_specs.delete_if {|spec| spec.fields==["category_id"]}
308
+ {.hidden}
309
+
310
+ ### Timestamps
311
+
312
+ `updated_at` and `created_at` can be declared with the shorthand `timestamps`
313
+
314
+ >>
315
+ class Advert
316
+ fields do
317
+ timestamps
318
+ end
319
+ end
320
+ >> up, down = Generators::Hobo::Migration::Migrator.run
321
+ >> up
322
+ =>
323
+ "add_column :adverts, :created_at, :datetime
324
+ add_column :adverts, :updated_at, :datetime"
325
+ >> down
326
+ =>
327
+ "remove_column :adverts, :created_at
328
+ remove_column :adverts, :updated_at"
329
+ >>
330
+
331
+ Cleanup:
332
+ {.hidden}
333
+
334
+ >> Advert.field_specs.delete(:updated_at)
335
+ >> Advert.field_specs.delete(:created_at)
336
+ {.hidden}
337
+
338
+ ### Indices
339
+
340
+ You can add an index to a field definition
341
+
342
+ >>
343
+ class Advert
344
+ fields do
345
+ title :string, :index => true
346
+ end
347
+ end
348
+ >> up, down = Generators::Hobo::Migration::Migrator.run
349
+ >> up.split("\n")[2]
350
+ => 'add_index :adverts, [:title]'
351
+
352
+ Cleanup:
353
+ {.hidden}
354
+
355
+ >> Advert.index_specs.delete_if {|spec| spec.fields==["title"]}
356
+ {.hidden}
357
+
358
+ You can ask for a unique index
359
+
360
+ >>
361
+ class Advert
362
+ fields do
363
+ title :string, :index => true, :unique => true
364
+ end
365
+ end
366
+ >> up, down = Generators::Hobo::Migration::Migrator.run
367
+ >> up.split("\n")[2]
368
+ => 'add_index :adverts, [:title], :unique => true'
369
+
370
+ Cleanup:
371
+ {.hidden}
372
+
373
+ >> Advert.index_specs.delete_if {|spec| spec.fields==["title"]}
374
+ {.hidden}
375
+
376
+ You can specify the name for the index
377
+
378
+ >>
379
+ class Advert
380
+ fields do
381
+ title :string, :index => 'my_index'
382
+ end
383
+ end
384
+ >> up, down = Generators::Hobo::Migration::Migrator.run
385
+ >> up.split("\n")[2]
386
+ => "add_index :adverts, [:title], :name => 'my_index'"
387
+
388
+ Cleanup:
389
+ {.hidden}
390
+
391
+ >> Advert.index_specs.delete_if {|spec| spec.fields==["title"]}
392
+ {.hidden}
393
+
394
+ You can ask for an index outside of the fields block
395
+
396
+ >>
397
+ class Advert
398
+ index :title
399
+ end
400
+ >> up, down = Generators::Hobo::Migration::Migrator.run
401
+ >> up.split("\n")[2]
402
+ => "add_index :adverts, [:title]"
403
+
404
+ Cleanup:
405
+ {.hidden}
406
+
407
+ >> Advert.index_specs.delete_if {|spec| spec.fields==["title"]}
408
+ {.hidden}
409
+
410
+ The available options for the index function are `:unique` and `:name`
411
+
412
+ >>
413
+ class Advert
414
+ index :title, :unique => true, :name => 'my_index'
415
+ end
416
+ >> up, down = Generators::Hobo::Migration::Migrator.run
417
+ >> up.split("\n")[2]
418
+ => "add_index :adverts, [:title], :unique => true, :name => 'my_index'"
419
+
420
+ Cleanup:
421
+ {.hidden}
422
+
423
+ >> Advert.index_specs.delete_if {|spec| spec.fields==["title"]}
424
+ {.hidden}
425
+
426
+ You can create an index on more than one field
427
+
428
+ >>
429
+ class Advert
430
+ index [:title, :category_id]
431
+ end
432
+ >> up, down = Generators::Hobo::Migration::Migrator.run
433
+ >> up.split("\n")[2]
434
+ => "add_index :adverts, [:title, :category_id]"
435
+
436
+ Cleanup:
437
+ {.hidden}
438
+
439
+ >> Advert.index_specs.delete_if {|spec| spec.fields==["title", "category_id"]}
440
+ {.hidden}
441
+
442
+ Finally, you can specify that the migration generator should completely ignore an index by passing its name to ignore_index in the model. This is helpful for preserving indices that can't be automatically generated, such as prefix indices in MySQL.
443
+
444
+ ### Rename a table
445
+
446
+ 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.
447
+
448
+ >>
449
+ class Advert
450
+ set_table_name "ads"
451
+ fields do
452
+ title :string, :default => "Untitled"
453
+ body :text
454
+ end
455
+ end
456
+ >> up, down = Generators::Hobo::Migration::Migrator.run(:adverts => :ads)
457
+ >> up
458
+ => "rename_table :adverts, :ads"
459
+ >> down
460
+ => "rename_table :ads, :adverts"
461
+
462
+ Set the table name back to what it should be and confirm we're in sync:
463
+
464
+ >> class Advert; set_table_name "adverts"; end
465
+ >> Generators::Hobo::Migration::Migrator.run
466
+ => ["", ""]
467
+
468
+ ### Rename a table
469
+
470
+ As with renaming columns, we have to tell the migration generator about the rename. Here we create a new class 'Advertisement', and tell ActiveRecord to forget about the Advert class. This requires code that shouldn't be shown to impressionable children.
471
+ {.hidden}
472
+
473
+ >>
474
+ def nuke_model_class(klass)
475
+ ActiveSupport::DescendantsTracker.instance_eval do
476
+ class_variable_get('@@direct_descendants')[ActiveRecord::Base].delete(klass)
477
+ end
478
+ Object.instance_eval { remove_const klass.name.to_sym }
479
+ end
480
+ >> nuke_model_class(Advert)
481
+ {.hidden}
482
+
483
+ >>
484
+ class Advertisement < ActiveRecord::Base
485
+ fields do
486
+ title :string, :default => "Untitled"
487
+ body :text
488
+ end
489
+ end
490
+ >> up, down = Generators::Hobo::Migration::Migrator.run(:adverts => :advertisements)
491
+ >> up
492
+ => "rename_table :adverts, :advertisements"
493
+ >> down
494
+ => "rename_table :advertisements, :adverts"
495
+
496
+ ### Drop a table
497
+
498
+ >> nuke_model_class(Advertisement)
499
+ {.hidden}
500
+
501
+ If you delete a model, the migration generator will create a `drop_table` migration.
502
+
503
+ Dropping tables is where the automatic down-migration really comes in handy:
504
+
505
+ >> up, down = Generators::Hobo::Migration::Migrator.run
506
+ >> up
507
+ => "drop_table :adverts"
508
+ >> down
509
+ =>
510
+ "create_table "adverts", :force => true do |t|
511
+ t.text "body"
512
+ t.string "title", :default => "Untitled"
513
+ end"
514
+
515
+ ## STI
516
+
517
+ ### Adding an STI subclass
518
+
519
+ Adding a subclass or two should introduce the 'type' column and no other changes
520
+
521
+ >>
522
+ class Advert < ActiveRecord::Base
523
+ fields do
524
+ body :text
525
+ title :string, :default => "Untitled"
526
+ end
527
+ end
528
+ class FancyAdvert < Advert
529
+ end
530
+ class SuperFancyAdvert < FancyAdvert
531
+ end
532
+ >> up, down = Generators::Hobo::Migration::Migrator.run
533
+ >> up
534
+ =>
535
+ "add_column :adverts, :type, :string
536
+
537
+ add_index :adverts, [:type]"
538
+ >> down
539
+ =>
540
+ "remove_column :adverts, :type
541
+
542
+ remove_index :adverts, :name => :index_adverts_on_type rescue ActiveRecord::StatementInvalid"
543
+
544
+ Cleanup
545
+ {.hidden}
546
+
547
+ >> Advert.field_specs.delete(:type)
548
+ >> nuke_model_class(SuperFancyAdvert)
549
+ >> nuke_model_class(FancyAdvert)
550
+ >> Advert.index_specs.delete_if {|spec| spec.fields==["type"]}
551
+ {.hidden}
552
+
553
+
554
+ ## Coping with multiple changes
555
+
556
+ The migration generator is designed to create complete migrations even if many changes to the models have taken place.
557
+
558
+ First let's confirm we're in a known state. One model, 'Advert', with a string 'title' and text 'body':
559
+
560
+ >> Advert.connection.tables
561
+ => ["adverts"]
562
+ >> Advert.columns.*.name
563
+ => ["id", "body", "title"]
564
+ >> Generators::Hobo::Migration::Migrator.run
565
+ => ["", ""]
566
+
567
+
568
+ ### Rename a column and change the default
569
+
570
+ >> Advert.field_specs.clear
571
+ >>
572
+ class Advert
573
+ fields do
574
+ name :string, :default => "No Name"
575
+ body :text
576
+ end
577
+ end
578
+ >> up, down = Generators::Hobo::Migration::Migrator.run(:adverts => {:title => :name})
579
+ >> up
580
+ =>
581
+ "rename_column :adverts, :title, :name
582
+ change_column :adverts, :name, :string, :default => "No Name", :limit => 255"
583
+ >> down
584
+ =>
585
+ 'rename_column :adverts, :name, :title
586
+ change_column :adverts, :title, :string, :default => "Untitled"'
587
+
588
+
589
+ ### Rename a table and add a column
590
+
591
+ >> nuke_model_class(Advert)
592
+ {.hidden}
593
+
594
+ >>
595
+ class Ad < ActiveRecord::Base
596
+ fields do
597
+ title :string, :default => "Untitled"
598
+ body :text
599
+ created_at :datetime
600
+ end
601
+ end
602
+ >> up, down = Generators::Hobo::Migration::Migrator.run(:adverts => :ads)
603
+ >> up
604
+ =>
605
+ "rename_table :adverts, :ads
606
+
607
+ add_column :ads, :created_at, :datetime"
608
+
609
+ >>
610
+ class Advert < ActiveRecord::Base
611
+ fields do
612
+ body :text
613
+ title :string, :default => "Untitled"
614
+ end
615
+ end
616
+ {.hidden}
617
+
618
+ ## Legacy Keys
619
+
620
+ HoboFields has some support for legacy keys.
621
+
622
+ >> Advert.field_specs.clear
623
+ >>
624
+ class Advert
625
+ fields do
626
+ name :string, :default => "No Name"
627
+ body :text
628
+ end
629
+ set_primary_key "advert_id"
630
+ end
631
+ >> up, down = Generators::Hobo::Migration::Migrator.run(:adverts => {:id => :advert_id})
632
+ >> up
633
+ =>
634
+ "rename_column :adverts, :id, :advert_id
635
+
636
+ >> nuke_model_class(Advert)
637
+ >> nuke_model_class(Ad)
638
+ >> ActiveRecord::Base.connection.execute "drop table `adverts`;"
639
+ {.hidden}
@@ -0,0 +1,75 @@
1
+ # HoboFields - Migration Generator Comments
2
+
3
+ Our test requires to prepare the testapp for a different environment:
4
+ {.hidden}
5
+
6
+ doctest_require: ENV["RAILS_ENV"] = 'mysql_test'; 'prepare_testapp'
7
+
8
+ >> system "cd #{TESTAPP_PATH} && rake db:setup"
9
+ => true
10
+
11
+ >> p Rails.env
12
+ >>
13
+ def nuke_model_class(klass)
14
+ ActiveSupport::DescendantsTracker.instance_eval do
15
+ class_variable_get('@@direct_descendants')[ActiveRecord::Base].delete(klass)
16
+ end
17
+ Object.instance_eval { remove_const klass.name.to_sym }
18
+ end
19
+
20
+ {.hidden}
21
+
22
+
23
+ ## Comments
24
+
25
+ Comments can be added to tables and fields with HoboFields.
26
+
27
+ >>
28
+ class Product < ActiveRecord::Base
29
+ fields do
30
+ name :string, :comment => "short name"
31
+ description :string
32
+ end
33
+ end
34
+ >> Rails.env
35
+ => "mysql_test"
36
+ >> Rails::Generators.invoke 'hobo:migration', %w(-n -m)
37
+
38
+ These comments will be saved to your schema if you have the [column_comments](http://github.com/bryanlarsen/column_comments) plugin installed. If you do not have this plugin installed, the comments will be available by querying `field_specs`:
39
+
40
+ >> Product.field_specs["name"].comment
41
+ => "short name"
42
+
43
+ The plugin [activerecord-comments](http://github.com/bryanlarsen/activerecord-comments) may be used to get the comments from the database directly. If the plugin is installed, use this instead:
44
+
45
+ Product.column("name").comment
46
+
47
+ Because it will be quite common for people not to have both [column_comments](http://github.com/bryanlarsen/column_comments) and [activerecord-comments](http://github.com/bryanlarsen/activerecord-comments) installed, it is impossible for HoboFields to determine the difference between no previous comment and a previously missing plugin. Therefore, HoboFields will not generate a migration if the only change was to add a comment. HoboFields will generate a migration for a comment change, but only if the plugin is installed.
48
+
49
+ >> require 'activerecord-comments'
50
+
51
+ >> # manually add comment as the column_comments plugin would
52
+ >> Product.connection.execute "alter table `products` modify `name` varchar(255) default null comment 'short name';"
53
+
54
+ >>
55
+ class Product < ActiveRecord::Base
56
+ fields do
57
+ name :string, :comment => "Short namex"
58
+ description :string, :comment => "Long name"
59
+ end
60
+ end
61
+ >> up, down = Generators::Hobo::Migration::Migrator.run
62
+ >> up
63
+ >> up.split(',').slice(0,3).join(',')
64
+ => 'change_column :products, :name, :string'
65
+ >> up.split(',').slice(3,2).sort.join(',')
66
+ => " :comment => \"Short namex\", :limit => 255"
67
+
68
+
69
+ Cleanup
70
+ {.hidden}
71
+
72
+ >> nuke_model_class(Product)
73
+ >> ActiveRecord::Base.connection.execute "drop table `products`;"
74
+ >> system "cd #{TESTAPP_PATH} && rake db:drop RAILS_ENV=mysql_test"
75
+ {.hidden}