hobofields 0.7.5 → 0.8
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/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'
|