hobofields 0.7.5 → 0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest +12 -3
- data/hobofields.gemspec +23 -9
- data/lib/hobo_fields/email_address.rb +6 -6
- data/lib/hobo_fields/enum_string.rb +16 -16
- data/lib/hobo_fields/field_declaration_dsl.rb +11 -11
- data/lib/hobo_fields/field_spec.rb +19 -19
- data/lib/hobo_fields/fields_declaration.rb +5 -5
- data/lib/hobo_fields/html_string.rb +4 -6
- data/lib/hobo_fields/markdown_string.rb +3 -3
- data/lib/hobo_fields/migration_generator.rb +86 -66
- data/lib/hobo_fields/model_extensions.rb +43 -40
- data/lib/hobo_fields/password_string.rb +5 -5
- data/lib/hobo_fields/text.rb +9 -9
- data/lib/hobo_fields/textile_string.rb +3 -12
- data/lib/hobo_fields.rb +33 -32
- data/{generators → rails_generators}/hobo_migration/hobo_migration_generator.rb +46 -20
- data/{generators → rails_generators}/hobo_migration/templates/migration.rb +0 -0
- data/rails_generators/hobofield_model/USAGE +29 -0
- data/rails_generators/hobofield_model/hobofield_model_generator.rb +38 -0
- data/rails_generators/hobofield_model/templates/fixtures.yml.erb +19 -0
- data/rails_generators/hobofield_model/templates/model.rb.erb +10 -0
- data/rails_generators/hobofield_model/templates/test.rb.erb +8 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/hobofields.rdoctest +65 -28
- data/test/hobofields_api.rdoctest +28 -27
- data/test/migration_generator.rdoctest +75 -43
- data/test/rich_types.rdoctest +26 -26
- data/test/test_generator_helper.rb +29 -0
- data/test/test_hobofield_model_generator.rb +65 -0
- metadata +29 -10
@@ -9,6 +9,7 @@ In order for the API examples to run we need a connection to a database. You can
|
|
9
9
|
>> Dependencies.load_paths << '.'
|
10
10
|
>> Dependencies.mechanism = :require
|
11
11
|
>> require 'activerecord'
|
12
|
+
>> require 'action_controller'
|
12
13
|
>> require 'hobofields'
|
13
14
|
>> mysql_database = "hobofields_doctest"
|
14
15
|
>> system("mysqladmin create #{mysql_database}") or raise "could not create database"
|
@@ -35,7 +36,7 @@ The migration generator uses this information to create a migration. The followi
|
|
35
36
|
|
36
37
|
>> up, down = HoboFields::MigrationGenerator.run
|
37
38
|
>> ActiveRecord::Migration.class_eval up
|
38
|
-
|
39
|
+
|
39
40
|
We're now ready to start demonstrating the API
|
40
41
|
|
41
42
|
## The Basics
|
@@ -49,7 +50,7 @@ Field values are returned as the type you specify.
|
|
49
50
|
>> a = Advert.new :body => "This is the body"
|
50
51
|
>> a.body.class
|
51
52
|
=> HoboFields::Text
|
52
|
-
|
53
|
+
|
53
54
|
This also works after a round-trip to the database
|
54
55
|
|
55
56
|
>> a.save
|
@@ -57,35 +58,35 @@ This also works after a round-trip to the database
|
|
57
58
|
>> b.body.class
|
58
59
|
=> HoboFields::Text
|
59
60
|
|
60
|
-
HoboFields::Text is a simple subclass of string. It's a "wrapper type", by which we mean you pass the underlying value to the constructor.
|
61
|
+
HoboFields::Text is a simple subclass of string. It's a "wrapper type", by which we mean you pass the underlying value to the constructor.
|
61
62
|
|
62
63
|
>> t = HoboFields::Text.new("hello")
|
63
64
|
=> "hello"
|
64
65
|
>> t.class
|
65
66
|
=> HoboFields::Text
|
66
|
-
|
67
|
+
|
67
68
|
If you define your own rich types, they need to support a one argument constructor in the same way.
|
68
69
|
|
69
70
|
Although the body of our advert is really just a string, it's very useful that it has a different type. For example, the view layer in Hobo Rapid would use this information to render a `<textarea>` rather than an `<input type='text'>` in an Advert form.
|
70
|
-
|
71
|
+
|
71
72
|
|
72
73
|
## Names vs. Classes
|
73
74
|
|
74
75
|
In the `fields do ... end` block you can give the field-type either as a name (symbol) or a class. For example, we could have said
|
75
76
|
|
76
77
|
body HoboFields::Text
|
77
|
-
|
78
|
+
|
78
79
|
Obviously the symbol form is a nicer:
|
79
80
|
|
80
81
|
body :text
|
81
82
|
|
82
|
-
If you provide a class it must define the `COLUMN_TYPE` constant. This instructs the migration generator to create the appropriate underlying database column type. It should be a symbol that is a valid column type in a Rails migration.
|
83
|
+
If you provide a class it must define the `COLUMN_TYPE` constant. This instructs the migration generator to create the appropriate underlying database column type. It should be a symbol that is a valid column type in a Rails migration.
|
83
84
|
|
84
85
|
>> HoboFields::Text::COLUMN_TYPE
|
85
86
|
=> :text
|
86
|
-
|
87
|
+
|
87
88
|
The full set of available symbolic names is
|
88
|
-
|
89
|
+
|
89
90
|
* `:integer`
|
90
91
|
* `:big_integer`
|
91
92
|
* `:float`
|
@@ -104,9 +105,9 @@ You can add your own types too. More on that later.
|
|
104
105
|
|
105
106
|
|
106
107
|
## Model extensions
|
107
|
-
|
108
|
+
|
108
109
|
HoboFields adds a few features to your models.
|
109
|
-
|
110
|
+
|
110
111
|
### `Model.attr_type`
|
111
112
|
|
112
113
|
Returns the type (i.e. class) declared for a given field or attribute
|
@@ -115,7 +116,7 @@ Returns the type (i.e. class) declared for a given field or attribute
|
|
115
116
|
=> String
|
116
117
|
>> Advert.attr_type :body
|
117
118
|
=> HoboFields::Text
|
118
|
-
|
119
|
+
|
119
120
|
### `Model.column`
|
120
121
|
|
121
122
|
A shorthand for accessing column metadata
|
@@ -136,9 +137,9 @@ In your HoboFields models you can also give type information to "virtual fields"
|
|
136
137
|
end
|
137
138
|
>> a = Advert.new
|
138
139
|
>> a.my_attr = "hello"
|
139
|
-
>> a.my_attr.class
|
140
|
+
>> a.my_attr.class
|
140
141
|
=> HoboFields::Text
|
141
|
-
|
142
|
+
|
142
143
|
|
143
144
|
## Field validations
|
144
145
|
|
@@ -148,7 +149,7 @@ HoboFields gives you some shorthands for declaring some common validations right
|
|
148
149
|
|
149
150
|
The `:required` argument to a field gives a `validates_presence_of`:
|
150
151
|
|
151
|
-
>>
|
152
|
+
>>
|
152
153
|
class Advert
|
153
154
|
fields do
|
154
155
|
title :string, :required
|
@@ -162,13 +163,13 @@ The `:required` argument to a field gives a `validates_presence_of`:
|
|
162
163
|
>> a.title = "Jimbo"
|
163
164
|
>> a.save
|
164
165
|
=> true
|
165
|
-
|
166
|
+
|
166
167
|
|
167
168
|
### Unique fields
|
168
169
|
|
169
170
|
The `:unique` argument in a field declaration gives `validates_uniqueness_of`:
|
170
|
-
|
171
|
-
>>
|
171
|
+
|
172
|
+
>>
|
172
173
|
class Advert < ActiveRecord::Base
|
173
174
|
fields do
|
174
175
|
title :string, :unique
|
@@ -182,9 +183,9 @@ The `:unique` argument in a field declaration gives `validates_uniqueness_of`:
|
|
182
183
|
>> a.title = "Sambo"
|
183
184
|
>> a.save
|
184
185
|
=> true
|
185
|
-
|
186
|
+
|
186
187
|
Let's get back to the basic Advert class with no validations before we continue:
|
187
|
-
|
188
|
+
|
188
189
|
>> Dependencies.remove_constant "Advert"
|
189
190
|
>>
|
190
191
|
class Advert < ActiveRecord::Base
|
@@ -195,7 +196,7 @@ Let's get back to the basic Advert class with no validations before we continue:
|
|
195
196
|
end
|
196
197
|
end
|
197
198
|
|
198
|
-
|
199
|
+
|
199
200
|
### Type specific validations
|
200
201
|
|
201
202
|
Rich types can define there own validations by a `#validate` method. It should return an error message if the value is invalid, otherwise nil. We can call that method directly to show how it works:
|
@@ -205,9 +206,9 @@ Rich types can define there own validations by a `#validate` method. It should r
|
|
205
206
|
=> HoboFields::EmailAddress
|
206
207
|
>> a.contact_address.validate
|
207
208
|
=> "is not valid"
|
208
|
-
|
209
|
+
|
209
210
|
But normally that method would be called for us during validation:
|
210
|
-
|
211
|
+
|
211
212
|
>> a.valid?
|
212
213
|
=> false
|
213
214
|
>> a.errors.full_messages
|
@@ -215,9 +216,9 @@ But normally that method would be called for us during validation:
|
|
215
216
|
>> a.contact_address = "me@me.com"
|
216
217
|
>> a.valid?
|
217
218
|
=> true
|
218
|
-
|
219
|
+
|
219
220
|
You can add this capability to your own rich types just by defining `#validate`
|
220
|
-
|
221
|
+
|
221
222
|
### Validating virtual fields
|
222
223
|
|
223
224
|
You can set the type of a virtual field to a rich type, e.g.
|
@@ -226,7 +227,7 @@ You can set the type of a virtual field to a rich type, e.g.
|
|
226
227
|
class Advert
|
227
228
|
attr_accessor :alternative_email, :type => :email_address
|
228
229
|
end
|
229
|
-
|
230
|
+
|
230
231
|
By default, virtual fields are not subject to validation.
|
231
232
|
|
232
233
|
>> a = Advert.new :alternative_email => "woot!"
|
@@ -241,7 +242,7 @@ To have them validated use `validate_virtual_field`:
|
|
241
242
|
end
|
242
243
|
>> a.valid?
|
243
244
|
=> false
|
244
|
-
|
245
|
+
|
245
246
|
## Cleanup
|
246
247
|
|
247
248
|
>> system "mysqladmin --force drop #{mysql_database}"
|
@@ -8,10 +8,11 @@ Firstly, in order to test the migration generator outside of a full Rails stack,
|
|
8
8
|
>> require 'activesupport'
|
9
9
|
>> Dependencies.load_paths << '.'
|
10
10
|
>> Dependencies.mechanism = :require
|
11
|
-
|
11
|
+
|
12
12
|
And we'll require:
|
13
13
|
|
14
14
|
>> require 'activerecord'
|
15
|
+
>> require 'action_controller'
|
15
16
|
>> require 'hobofields'
|
16
17
|
|
17
18
|
We also need to get ActiveRecord set up with a database connection
|
@@ -36,12 +37,12 @@ The migration generator works by:
|
|
36
37
|
Normally you would run the migration generator as a regular Rails generator. You would type
|
37
38
|
|
38
39
|
$ script/generator hobo_migration
|
39
|
-
|
40
|
-
in your Rails app, and the migration file would be created in `db/migrate`.
|
41
40
|
|
42
|
-
|
41
|
+
in your Rails app, and the migration file would be created in `db/migrate`.
|
43
42
|
|
44
|
-
|
43
|
+
In order to demonstrate the generator in this doctest script however, we'll be using the Ruby API instead. The method `HoboFields::MigrationGenerator.run` returns a pair of strings -- the up migration and the down migration.
|
44
|
+
|
45
|
+
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.
|
45
46
|
|
46
47
|
>> HoboFields::MigrationGenerator.run
|
47
48
|
=> ["", ""]
|
@@ -59,8 +60,8 @@ The migration generator only takes into account classes that use HoboFields, i.e
|
|
59
60
|
### Create the table
|
60
61
|
|
61
62
|
Here we see a simple `create_table` migration along with the `drop_table` down migration
|
62
|
-
|
63
|
-
>>
|
63
|
+
|
64
|
+
>>
|
64
65
|
class Advert < ActiveRecord::Base
|
65
66
|
fields do
|
66
67
|
name :string
|
@@ -74,20 +75,20 @@ Here we see a simple `create_table` migration along with the `drop_table` down m
|
|
74
75
|
end
|
75
76
|
>> down
|
76
77
|
=> "drop_table :adverts"
|
77
|
-
|
78
|
+
|
78
79
|
Normally we would run the generated migration with `rake db:create`. We can achieve the same effect directly in Ruby like this:
|
79
80
|
|
80
81
|
>> ActiveRecord::Migration.class_eval up
|
81
82
|
>> Advert.columns.*.name
|
82
83
|
=> ["id", "name"]
|
83
|
-
|
84
|
+
|
84
85
|
We'll define a method to make that easier next time
|
85
86
|
|
86
87
|
>>
|
87
88
|
def migrate(renames={})
|
88
89
|
up, down = HoboFields::MigrationGenerator.run(renames)
|
89
90
|
puts up
|
90
|
-
ActiveRecord::Migration.class_eval(up)
|
91
|
+
ActiveRecord::Migration.class_eval(up)
|
91
92
|
ActiveRecord::Base.send(:subclasses).each { |model| model.reset_column_information }
|
92
93
|
[up, down]
|
93
94
|
end
|
@@ -134,7 +135,7 @@ If we remove a field from the model, the migration generator removes the databas
|
|
134
135
|
=> "remove_column :adverts, :published_at"
|
135
136
|
>> down
|
136
137
|
=> "add_column :adverts, :published_at, :datetime"
|
137
|
-
|
138
|
+
|
138
139
|
### Rename a field
|
139
140
|
|
140
141
|
Here we rename the `name` field to `title`. By default the generator sees this as removing `name` and adding `title`.
|
@@ -157,7 +158,7 @@ Here we rename the `name` field to `title`. By default the generator sees this a
|
|
157
158
|
remove_column :adverts, :title
|
158
159
|
add_column :adverts, :name, :string
|
159
160
|
>>
|
160
|
-
|
161
|
+
|
161
162
|
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:
|
162
163
|
|
163
164
|
>> up, down = HoboFields::MigrationGenerator.run(:adverts => { :name => :title })
|
@@ -165,12 +166,12 @@ When run as a generator, the migration-generator won't make this assumption. Ins
|
|
165
166
|
=> "rename_column :adverts, :name, :title"
|
166
167
|
>> down
|
167
168
|
=> "rename_column :adverts, :title, :name"
|
168
|
-
|
169
|
+
|
169
170
|
Let's apply that change to the database
|
170
171
|
|
171
172
|
>> migrate
|
172
|
-
|
173
|
-
|
173
|
+
|
174
|
+
|
174
175
|
### Change a type
|
175
176
|
|
176
177
|
>> Advert.attr_type :title
|
@@ -184,13 +185,13 @@ Let's apply that change to the database
|
|
184
185
|
end
|
185
186
|
>> up, down = HoboFields::MigrationGenerator.run
|
186
187
|
>> up
|
187
|
-
=> "change_column :adverts, :title, :text"
|
188
|
+
=> "change_column :adverts, :title, :text, :limit => nil"
|
188
189
|
>> down
|
189
190
|
=> "change_column :adverts, :title, :string"
|
190
|
-
|
191
|
-
|
191
|
+
|
192
|
+
|
192
193
|
### Add a default
|
193
|
-
|
194
|
+
|
194
195
|
>>
|
195
196
|
class Advert
|
196
197
|
fields do
|
@@ -200,11 +201,41 @@ Let's apply that change to the database
|
|
200
201
|
end
|
201
202
|
>> up, down = migrate
|
202
203
|
>> up
|
203
|
-
=> 'change_column :adverts, :title, :string, :default => "Untitled"'
|
204
|
+
=> 'change_column :adverts, :title, :string, :default => "Untitled", :limit => 255'
|
204
205
|
>> down
|
205
206
|
=> "change_column :adverts, :title, :string"
|
207
|
+
|
208
|
+
|
209
|
+
### Limits
|
210
|
+
|
211
|
+
>>
|
212
|
+
class Advert
|
213
|
+
fields do
|
214
|
+
price :integer, :limit => 4
|
215
|
+
end
|
216
|
+
end
|
217
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
218
|
+
>> up
|
219
|
+
"add_column :advert, :integer, :limit => 4"
|
206
220
|
|
207
221
|
|
222
|
+
Note that limit on a decimal column is ignored (use :scale and :precision)
|
223
|
+
|
224
|
+
>>
|
225
|
+
class Advert
|
226
|
+
fields do
|
227
|
+
price :decimal, :limit => 4
|
228
|
+
end
|
229
|
+
end
|
230
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
231
|
+
>> up
|
232
|
+
=> "add_column :adverts, :price, :decimal"
|
233
|
+
|
234
|
+
Cleanup
|
235
|
+
|
236
|
+
>> Advert.field_specs.delete :price
|
237
|
+
|
238
|
+
|
208
239
|
### Foreign Keys
|
209
240
|
|
210
241
|
HoboFields extends the `belongs_to` macro so that it also declares the foreign-key field.
|
@@ -218,13 +249,13 @@ HoboFields extends the `belongs_to` macro so that it also declares the foreign-k
|
|
218
249
|
=> 'add_column :adverts, :category_id, :integer'
|
219
250
|
>> down
|
220
251
|
=> 'remove_column :adverts, :category_id'
|
221
|
-
|
252
|
+
|
222
253
|
Cleanup:
|
223
254
|
|
224
255
|
>> Advert.field_specs.delete(:category_id)
|
225
|
-
|
256
|
+
|
226
257
|
If you specify a custom foreign key, the migration generator observes that:
|
227
|
-
|
258
|
+
|
228
259
|
>>
|
229
260
|
class Advert
|
230
261
|
belongs_to :category, :foreign_key => "c_id"
|
@@ -234,12 +265,12 @@ If you specify a custom foreign key, the migration generator observes that:
|
|
234
265
|
=> 'add_column :adverts, :c_id, :integer'
|
235
266
|
>> down
|
236
267
|
=> 'remove_column :adverts, :c_id'
|
237
|
-
|
268
|
+
|
238
269
|
Cleanup:
|
239
270
|
|
240
271
|
>> Advert.field_specs.delete(:c_id)
|
241
|
-
|
242
|
-
|
272
|
+
|
273
|
+
|
243
274
|
### Timestamps
|
244
275
|
|
245
276
|
`updated_at` and `created_at` can be declared with the shorthand `timestamps`
|
@@ -252,7 +283,7 @@ Cleanup:
|
|
252
283
|
end
|
253
284
|
>> up, down = HoboFields::MigrationGenerator.run
|
254
285
|
>> up
|
255
|
-
=>""
|
286
|
+
=>""
|
256
287
|
add_column :adverts, :created_at, :datetime
|
257
288
|
add_column :adverts, :updated_at, :datetime
|
258
289
|
>> down
|
@@ -260,13 +291,13 @@ Cleanup:
|
|
260
291
|
remove_column :adverts, :created_at
|
261
292
|
remove_column :adverts, :updated_at
|
262
293
|
>>
|
263
|
-
|
294
|
+
|
264
295
|
Cleanup:
|
265
296
|
|
266
297
|
>> Advert.field_specs.delete(:updated_at)
|
267
298
|
>> Advert.field_specs.delete(:created_at)
|
268
299
|
|
269
|
-
|
300
|
+
|
270
301
|
### Rename a table
|
271
302
|
|
272
303
|
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.
|
@@ -284,7 +315,7 @@ The migration generator respects the `set_table_name` declaration, although as b
|
|
284
315
|
=> "rename_table :adverts, :ads"
|
285
316
|
>> down
|
286
317
|
=> "rename_table :ads, :adverts"
|
287
|
-
|
318
|
+
|
288
319
|
Set the table name back to what it should be and confirm we're in sync:
|
289
320
|
|
290
321
|
>> class Advert; set_table_name "adverts"; end
|
@@ -296,7 +327,7 @@ Set the table name back to what it should be and confirm we're in sync:
|
|
296
327
|
If you delete a model, the migration generator will create a `drop_table` migration. Unfortunately there's no way to fully remove the Advert class we've defined from the doctest session, but we can tell the migration generator to ignore it.
|
297
328
|
|
298
329
|
>> HoboFields::MigrationGenerator.ignore_models = [ :advert ]
|
299
|
-
|
330
|
+
|
300
331
|
Dropping tables is where the automatic down-migration really comes in handy:
|
301
332
|
|
302
333
|
>> up, down = HoboFields::MigrationGenerator.run
|
@@ -309,7 +340,7 @@ Dropping tables is where the automatic down-migration really comes in handy:
|
|
309
340
|
t.string "title", :default => "Untitled"
|
310
341
|
end
|
311
342
|
>>
|
312
|
-
|
343
|
+
|
313
344
|
### Rename a table
|
314
345
|
|
315
346
|
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.
|
@@ -326,11 +357,11 @@ As with renaming columns, we have to tell the migration generator about the rena
|
|
326
357
|
=> "rename_table :adverts, :advertisements"
|
327
358
|
>> down
|
328
359
|
=> "rename_table :advertisements, :adverts"
|
329
|
-
|
360
|
+
|
330
361
|
Now that we've seen the renaming we'll switch the 'ignore' setting to ignore that 'Advertisements' class.
|
331
|
-
|
362
|
+
|
332
363
|
>> HoboFields::MigrationGenerator.ignore_models = [ :advertisement ]
|
333
|
-
|
364
|
+
|
334
365
|
## STI
|
335
366
|
|
336
367
|
### Adding an STI subclass
|
@@ -345,14 +376,14 @@ Adding a subclass should introduce the 'type' column and no other changes
|
|
345
376
|
=> "add_column :adverts, :type, :string"
|
346
377
|
>> down
|
347
378
|
=> "remove_column :adverts, :type"
|
348
|
-
|
379
|
+
|
349
380
|
Cleanup
|
350
381
|
|
351
382
|
>> Advert.field_specs.delete(:type)
|
352
383
|
|
353
384
|
|
354
385
|
## Coping with multiple changes
|
355
|
-
|
386
|
+
|
356
387
|
The migration generator is designed to create complete migrations even if many changes to the models have taken place.
|
357
388
|
|
358
389
|
First let's confirm we're in a known state. One model, 'Advert', with a string 'title' and text 'body':
|
@@ -363,7 +394,7 @@ First let's confirm we're in a known state. One model, 'Advert', with a string '
|
|
363
394
|
=> ["id", "body", "title"]
|
364
395
|
>> HoboFields::MigrationGenerator.run
|
365
396
|
=> ["", ""]
|
366
|
-
|
397
|
+
|
367
398
|
|
368
399
|
### Rename a column and change the default
|
369
400
|
|
@@ -379,16 +410,16 @@ First let's confirm we're in a known state. One model, 'Advert', with a string '
|
|
379
410
|
>> up
|
380
411
|
=>""
|
381
412
|
rename_column :adverts, :title, :name
|
382
|
-
change_column :adverts, :name, :string, :default => "No Name"
|
413
|
+
change_column :adverts, :name, :string, :default => "No Name", :limit => 255
|
383
414
|
>> down
|
384
415
|
=>""
|
385
416
|
rename_column :adverts, :name, :title
|
386
417
|
change_column :adverts, :title, :string, :default => "Untitled"
|
387
418
|
>>
|
388
|
-
|
389
|
-
|
419
|
+
|
420
|
+
|
390
421
|
### Rename a table and add a column
|
391
|
-
|
422
|
+
|
392
423
|
>> HoboFields::MigrationGenerator.ignore_models << :advert
|
393
424
|
class Ad < ActiveRecord::Base
|
394
425
|
fields do
|
@@ -401,9 +432,10 @@ First let's confirm we're in a known state. One model, 'Advert', with a string '
|
|
401
432
|
>> up
|
402
433
|
=>""
|
403
434
|
rename_table :adverts, :ads
|
404
|
-
|
435
|
+
|
405
436
|
add_column :ads, :created_at, :datetime
|
406
437
|
>>
|
438
|
+
|
407
439
|
|
408
440
|
## Cleanup
|
409
441
|
|
data/test/rich_types.rdoctest
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
# HoboFields -- Rich Types
|
2
2
|
|
3
3
|
This doctest describes the rich types bundles with HoboFields, and the process by which you can create and register your own types.
|
4
|
-
|
4
|
+
|
5
5
|
>> require 'rubygems'
|
6
6
|
>> require 'activesupport'
|
7
7
|
>> Dependencies.load_paths << '.'
|
8
8
|
>> Dependencies.mechanism = :require
|
9
9
|
>> require 'activerecord'
|
10
10
|
>> require 'hobofields'
|
11
|
-
|
11
|
+
|
12
12
|
|
13
13
|
## `to_html` method
|
14
14
|
|
15
15
|
The rich types provide a `to_html` method. If you are using the full Hobo stack you don't need to be aware of this unless you're defining your own rich types -- the `<view>` tag uses `to_html` to render a rich type. If you are not using DRYML and Rapid, you can simply call `to_html` in your views, e.g.
|
16
|
-
|
16
|
+
|
17
17
|
<div class="post-body"><%= @post.body.to_html %></div>
|
18
|
-
|
18
|
+
|
19
19
|
If you ever decide to change from, say, plain text to markdown formatted, your view won't need to change.
|
20
20
|
|
21
21
|
## Defining your own Rich Type
|
@@ -24,25 +24,25 @@ Defining a rich type is very simple. We'll show an example here before we go thr
|
|
24
24
|
|
25
25
|
This class defines the methods `to_html` to customize the way the type is rendered, and `validate` to provide a custom validation. It also defined the `COLUMN_TYPE` constant to tell the migration generator what underlying type should represent these values in the database.
|
26
26
|
|
27
|
-
# Loud text always renderd in caps.
|
28
|
-
# It's rude to shout too much so it's not allowed to be
|
27
|
+
# Loud text always renderd in caps.
|
28
|
+
# It's rude to shout too much so it's not allowed to be
|
29
29
|
# longer than 100 characters
|
30
30
|
class LoudText < String
|
31
|
-
|
31
|
+
|
32
32
|
COLUMN_TYPE = :string
|
33
33
|
|
34
34
|
HoboFields.register_type(:loud, self)
|
35
|
-
|
35
|
+
|
36
36
|
def validate
|
37
37
|
"is too long (you shouldn't shout that much)" if length > 100
|
38
38
|
end
|
39
|
-
|
40
|
-
def to_html
|
39
|
+
|
40
|
+
def to_html(xmldoctype = true)
|
41
41
|
upcase
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
That's all there is to it. Defining `to_html` and `validate` are optional, defining `COLUMN_TYPE` and calling `HoboFields.register_type` are not.
|
47
47
|
|
48
48
|
|
@@ -77,13 +77,13 @@ Provides validation of correct email address format.
|
|
77
77
|
>> require 'bluecloth'
|
78
78
|
>> markdown = HoboFields::MarkdownString.new %(
|
79
79
|
# This is a heading
|
80
|
-
|
80
|
+
|
81
81
|
And text can be *emphasised*
|
82
82
|
)
|
83
83
|
>> markdown.to_html
|
84
84
|
=>""
|
85
85
|
<h1>This is a heading</h1>
|
86
|
-
|
86
|
+
|
87
87
|
<p>And text can be <em>emphasised</em></p>
|
88
88
|
>>
|
89
89
|
|
@@ -105,7 +105,7 @@ Provides validation of correct email address format.
|
|
105
105
|
`HoboFields::Text` provides a `to_html` method with HTML escaping and conversion of newlines to `<br />` tags.
|
106
106
|
|
107
107
|
>> text = HoboFields::Text.new %(Tom & Jerry
|
108
|
-
|
108
|
+
|
109
109
|
Cat & Mouse)
|
110
110
|
>> text.to_html
|
111
111
|
=>""
|
@@ -125,7 +125,7 @@ Provides validation of correct email address format.
|
|
125
125
|
|
126
126
|
>> ArticleStatus = HoboFields::EnumString.for(:draft, :approved, :published)
|
127
127
|
=> ArticleStatus
|
128
|
-
|
128
|
+
|
129
129
|
Note that, like all dynamically created classes in Ruby, the class is anonymous until assigned to a constant:
|
130
130
|
|
131
131
|
>> klass = HoboFields::EnumString.for(:draft, :approved, :published)
|
@@ -133,14 +133,14 @@ Note that, like all dynamically created classes in Ruby, the class is anonymous
|
|
133
133
|
>> AritcleStatus = klass
|
134
134
|
>> ArticleStatus
|
135
135
|
=> ArticleStatus
|
136
|
-
|
136
|
+
|
137
137
|
The values in the enum are available as class constants:
|
138
138
|
|
139
139
|
>> ArticleStatus::DRAFT
|
140
140
|
=> "draft"
|
141
141
|
>> ArticleStatus::DRAFT.class
|
142
142
|
=> ArticleStatus
|
143
|
-
|
143
|
+
|
144
144
|
There are also instance methods to check for each of the values:
|
145
145
|
|
146
146
|
>> a = ArticleStatus::APPROVED
|
@@ -148,29 +148,29 @@ There are also instance methods to check for each of the values:
|
|
148
148
|
=> false
|
149
149
|
>> a.is_approved?
|
150
150
|
=> true
|
151
|
-
|
151
|
+
|
152
152
|
They can be constructed from strings:
|
153
153
|
|
154
154
|
>> a = ArticleStatus.new("approved")
|
155
155
|
>> a.is_approved?
|
156
156
|
=> true
|
157
|
-
|
157
|
+
|
158
158
|
Equality is string equality, with symbols first converted to strings:
|
159
159
|
|
160
160
|
>> a == "approved"
|
161
161
|
=> true
|
162
162
|
>> a == :approved
|
163
163
|
=> true
|
164
|
-
|
165
|
-
|
164
|
+
|
165
|
+
|
166
166
|
Note that every enum you create is a subclass of HoboFields::EnumString:
|
167
167
|
|
168
168
|
>> a.is_a?(HoboFields::EnumString)
|
169
169
|
=> true
|
170
|
-
|
171
|
-
|
170
|
+
|
171
|
+
|
172
172
|
### Using EnumString in your models
|
173
|
-
|
173
|
+
|
174
174
|
`HoboFields::EnumString` extends the field declaration DSL with a shorthand for creating enum types:
|
175
175
|
|
176
176
|
>>
|
@@ -193,7 +193,7 @@ Sometimes it's nice to have a proper type name. Here's one way you might go abou
|
|
193
193
|
end
|
194
194
|
>> Article.attr_type :status
|
195
195
|
=> Article::Status
|
196
|
-
|
196
|
+
|
197
197
|
|
198
198
|
|
199
199
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
begin
|
2
|
+
require File.dirname(__FILE__) + '/test_helper'
|
3
|
+
rescue LoadError
|
4
|
+
require 'test/unit'
|
5
|
+
end
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
# Must set before requiring generator libs.
|
9
|
+
TMP_ROOT = File.dirname(__FILE__) + "/tmp" unless defined?(TMP_ROOT)
|
10
|
+
PROJECT_NAME = "myproject" unless defined?(PROJECT_NAME)
|
11
|
+
app_root = File.join(TMP_ROOT, PROJECT_NAME)
|
12
|
+
if defined?(APP_ROOT)
|
13
|
+
APP_ROOT.replace(app_root)
|
14
|
+
else
|
15
|
+
APP_ROOT = app_root
|
16
|
+
end
|
17
|
+
if defined?(RAILS_ROOT)
|
18
|
+
RAILS_ROOT.replace(app_root)
|
19
|
+
else
|
20
|
+
RAILS_ROOT = app_root
|
21
|
+
end
|
22
|
+
|
23
|
+
begin
|
24
|
+
require 'rubigen'
|
25
|
+
rescue LoadError
|
26
|
+
require 'rubygems'
|
27
|
+
require 'rubigen'
|
28
|
+
end
|
29
|
+
require 'rubigen/helpers/generator_test_helper'
|