hobofields 0.7.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +38 -0
- data/LICENSE.txt +22 -0
- data/Manifest +25 -0
- data/README.txt +8 -0
- data/generators/hobo_migration/hobo_migration_generator.rb +95 -0
- data/generators/hobo_migration/templates/migration.rb +9 -0
- data/hobofields.gemspec +42 -0
- data/init.rb +4 -0
- data/lib/hobo_fields/email_address.rb +24 -0
- data/lib/hobo_fields/enum_string.rb +64 -0
- data/lib/hobo_fields/field_declaration_dsl.rb +29 -0
- data/lib/hobo_fields/field_spec.rb +72 -0
- data/lib/hobo_fields/fields_declaration.rb +22 -0
- data/lib/hobo_fields/html_string.rb +15 -0
- data/lib/hobo_fields/markdown_string.rb +13 -0
- data/lib/hobo_fields/migration_generator.rb +293 -0
- data/lib/hobo_fields/model_extensions.rb +172 -0
- data/lib/hobo_fields/password_string.rb +15 -0
- data/lib/hobo_fields/text.rb +17 -0
- data/lib/hobo_fields/textile_string.rb +29 -0
- data/lib/hobo_fields.rb +139 -0
- data/lib/hobofields.rb +1 -0
- data/test/hobofields.rdoctest +57 -0
- data/test/hobofields_api.rdoctest +247 -0
- data/test/migration_generator.rdoctest +410 -0
- data/test/rich_types.rdoctest +215 -0
- metadata +98 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
# HoboFields
|
2
|
+
|
3
|
+
## About Doctests
|
4
|
+
|
5
|
+
HoboFields is documented and tested using *doctests*. This is an idea that comes from Python that we've been experimenting with for Hobo. Whenever you see code-blocks that start "`>>`", read them as IRB sessions. The `rdoctest` tool extracts these and runs them to verify they behave as advertised.
|
6
|
+
|
7
|
+
Doctests are a great way to get both documentation and tests from the same source. We're still experimenting with exactly how this all works though, so if the docs seem strange in places, please bear with us!
|
8
|
+
|
9
|
+
## Introduction
|
10
|
+
|
11
|
+
HoboFields lives on GitHub: [http://github.com/tablatom/hobofields](http://github.com/tablatom/hobofields)
|
12
|
+
|
13
|
+
You can install it with git:
|
14
|
+
|
15
|
+
git clone git://github.com/tablatom/hobofields.git vendor/plugins/hobofields
|
16
|
+
|
17
|
+
Or subversion:
|
18
|
+
|
19
|
+
svn export svn://hobocentral.net/hobofields/tags/rel_0.7.4 vendor/plugins/hobofields
|
20
|
+
|
21
|
+
HoboFields provides two main features:
|
22
|
+
|
23
|
+
* An extension to ActiveRecord that provides rich field types such as "markdown text" or "email address"
|
24
|
+
* A generator that writes your migrations for you. Your migration writing days are over.
|
25
|
+
|
26
|
+
This is all done using a declaration of your fields that you put in your models, for example
|
27
|
+
|
28
|
+
class BlogPost < ActiveRecord::Base
|
29
|
+
fields do
|
30
|
+
title :string
|
31
|
+
body :text
|
32
|
+
end
|
33
|
+
end
|
34
|
+
{: .ruby}
|
35
|
+
|
36
|
+
**NOTE:** If you're going to use the migration generator outside of Hobo, do remember the `--skip-migration` option when generating your models:
|
37
|
+
|
38
|
+
./script/generate model post --skip-migration
|
39
|
+
|
40
|
+
|
41
|
+
## [Migration Generator](/hobofields/migration_generator)
|
42
|
+
|
43
|
+
Once you have declared your fields like this, you can run the following, at any time during the development of your project:
|
44
|
+
|
45
|
+
$ ./script/generate hobo_migration
|
46
|
+
|
47
|
+
The migration generator will create a migration to change from the schema that is currently in your database, to the schema that your models need. That's really all there is to it. Note that the migration generator is interactive -- it can't tell the difference between renaming something vs. adding one thing and removing another, so it has to ask you.
|
48
|
+
|
49
|
+
The [migration generator doctests](/hobofields/migration_generator) provide a lot more detail. They're not really that great as documentation because doctests run in a single irb session, and that doesn't fit well with the concept of a generator. Skip these unless you're really keen to see the details of the migration generator in action
|
50
|
+
|
51
|
+
## [HoboFields API](/hobofields/hobofields_api)
|
52
|
+
|
53
|
+
As well as the migration generator, HoboFields provides a bunch of small extensions to ActiveRecord. The [HoboFields API doctests](/hobofields/hobofields_api) provide a useful reference to these.
|
54
|
+
|
55
|
+
## [Rich Types](/hobofields/rich_types)
|
56
|
+
|
57
|
+
Documentation for the full set of rich types bundled with HoboFields, and how to create your own, is [here](/hobofields/rich_types).
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# HoboFields API
|
2
|
+
|
3
|
+
## Connect to the Database
|
4
|
+
|
5
|
+
In order for the API examples to run we need a connection to a database. You can ignore this if you're just looking for documentation.
|
6
|
+
|
7
|
+
>> require 'rubygems'
|
8
|
+
>> require 'activesupport'
|
9
|
+
>> Dependencies.load_paths << '.'
|
10
|
+
>> Dependencies.mechanism = :require
|
11
|
+
>> require 'activerecord'
|
12
|
+
>> require 'hobofields'
|
13
|
+
>> mysql_database = "hobofields_doctest"
|
14
|
+
>> system("mysqladmin create #{mysql_database}") or raise "could not create database"
|
15
|
+
>> ActiveRecord::Base.establish_connection(:adapter => "mysql",
|
16
|
+
:database => mysql_database,
|
17
|
+
:host => "localhost")
|
18
|
+
|
19
|
+
## Example Models
|
20
|
+
|
21
|
+
Let's define some example models that we can use to demonstrate the API. With HoboFields we define the model's fields, with their name and type, directly in the model like so:
|
22
|
+
|
23
|
+
>>
|
24
|
+
class Advert < ActiveRecord::Base
|
25
|
+
fields do
|
26
|
+
title :string
|
27
|
+
body :text
|
28
|
+
contact_address :email_address
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
(Note: `:email_address` is an example of a "Rich Type" provided by HoboFields -- more on those later)
|
33
|
+
|
34
|
+
The migration generator uses this information to create a migration. The following creates and runs the migration so we're ready to go.
|
35
|
+
|
36
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
37
|
+
>> ActiveRecord::Migration.class_eval up
|
38
|
+
|
39
|
+
We're now ready to start demonstrating the API
|
40
|
+
|
41
|
+
## The Basics
|
42
|
+
|
43
|
+
The main feature of HoboFields, aside from the migration generator, is the ability to declare rich types for your fields. For example, you can declare that a field is an email address, and the field will be automatically validated for correct email address syntax.
|
44
|
+
|
45
|
+
### Field Types
|
46
|
+
|
47
|
+
Field values are returned as the type you specify.
|
48
|
+
|
49
|
+
>> a = Advert.new :body => "This is the body"
|
50
|
+
>> a.body.class
|
51
|
+
=> HoboFields::Text
|
52
|
+
|
53
|
+
This also works after a round-trip to the database
|
54
|
+
|
55
|
+
>> a.save
|
56
|
+
>> b = Advert.find(a.id)
|
57
|
+
>> b.body.class
|
58
|
+
=> HoboFields::Text
|
59
|
+
|
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
|
+
|
62
|
+
>> t = HoboFields::Text.new("hello")
|
63
|
+
=> "hello"
|
64
|
+
>> t.class
|
65
|
+
=> HoboFields::Text
|
66
|
+
|
67
|
+
If you define your own rich types, they need to support a one argument constructor in the same way.
|
68
|
+
|
69
|
+
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
|
+
|
72
|
+
## Names vs. Classes
|
73
|
+
|
74
|
+
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
|
+
body HoboFields::Text
|
77
|
+
|
78
|
+
Obviously the symbol form is a nicer:
|
79
|
+
|
80
|
+
body :text
|
81
|
+
|
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
|
+
|
84
|
+
>> HoboFields::Text::COLUMN_TYPE
|
85
|
+
=> :text
|
86
|
+
|
87
|
+
The full set of available symbolic names is
|
88
|
+
|
89
|
+
* `:integer`
|
90
|
+
* `:big_integer`
|
91
|
+
* `:float`
|
92
|
+
* `:string`
|
93
|
+
* `:text`
|
94
|
+
* `:boolean`
|
95
|
+
* `:date`
|
96
|
+
* `:datetime`
|
97
|
+
* `:html`
|
98
|
+
* `:textile`
|
99
|
+
* `:markdown`
|
100
|
+
* `:password`
|
101
|
+
* `:email_addresss`
|
102
|
+
|
103
|
+
You can add your own types too. More on that later.
|
104
|
+
|
105
|
+
|
106
|
+
## Model extensions
|
107
|
+
|
108
|
+
HoboFields adds a few features to your models.
|
109
|
+
|
110
|
+
### `Model.attr_type`
|
111
|
+
|
112
|
+
Returns the type (i.e. class) declared for a given field or attribute
|
113
|
+
|
114
|
+
>> Advert.attr_type :title
|
115
|
+
=> String
|
116
|
+
>> Advert.attr_type :body
|
117
|
+
=> HoboFields::Text
|
118
|
+
|
119
|
+
### `Model.column`
|
120
|
+
|
121
|
+
A shorthand for accessing column metadata
|
122
|
+
|
123
|
+
>> col = Advert.column :title
|
124
|
+
>> col.name
|
125
|
+
"title"
|
126
|
+
>> col.klass
|
127
|
+
>> String
|
128
|
+
|
129
|
+
### `Model.attr_accessor` with types
|
130
|
+
|
131
|
+
In your HoboFields models you can also give type information to "virtual fields" (i.e. regular Ruby attributes)
|
132
|
+
|
133
|
+
>>
|
134
|
+
class Advert
|
135
|
+
attr_accessor :my_attr, :type => :text
|
136
|
+
end
|
137
|
+
>> a = Advert.new
|
138
|
+
>> a.my_attr = "hello"
|
139
|
+
>> a.my_attr.class
|
140
|
+
=> HoboFields::Text
|
141
|
+
|
142
|
+
|
143
|
+
## Field validations
|
144
|
+
|
145
|
+
HoboFields gives you some shorthands for declaring some common validations right in the field declaration
|
146
|
+
|
147
|
+
### Required fields
|
148
|
+
|
149
|
+
The `:required` argument to a field gives a `validates_presence_of`:
|
150
|
+
|
151
|
+
>>
|
152
|
+
class Advert
|
153
|
+
fields do
|
154
|
+
title :string, :required
|
155
|
+
end
|
156
|
+
end
|
157
|
+
>> a = Advert.new
|
158
|
+
>> a.valid?
|
159
|
+
=> false
|
160
|
+
>> a.errors.full_messages
|
161
|
+
=> ["Title can't be blank"]
|
162
|
+
>> a.title = "Jimbo"
|
163
|
+
>> a.save
|
164
|
+
=> true
|
165
|
+
|
166
|
+
|
167
|
+
### Unique fields
|
168
|
+
|
169
|
+
The `:unique` argument in a field declaration gives `validates_uniqueness_of`:
|
170
|
+
|
171
|
+
>>
|
172
|
+
class Advert < ActiveRecord::Base
|
173
|
+
fields do
|
174
|
+
title :string, :unique
|
175
|
+
end
|
176
|
+
end
|
177
|
+
>> a = Advert.new :title => "Jimbo"
|
178
|
+
>> a.valid?
|
179
|
+
=> false
|
180
|
+
>> a.errors.full_messages
|
181
|
+
=> ["Title has already been taken"]
|
182
|
+
>> a.title = "Sambo"
|
183
|
+
>> a.save
|
184
|
+
=> true
|
185
|
+
|
186
|
+
Let's get back to the basic Advert class with no validations before we continue:
|
187
|
+
|
188
|
+
>> Dependencies.remove_constant "Advert"
|
189
|
+
>>
|
190
|
+
class Advert < ActiveRecord::Base
|
191
|
+
fields do
|
192
|
+
title :string
|
193
|
+
body :text
|
194
|
+
contact_address :email_address
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
### Type specific validations
|
200
|
+
|
201
|
+
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:
|
202
|
+
|
203
|
+
>> a = Advert.new :contact_address => "not really an email address"
|
204
|
+
>> a.contact_address.class
|
205
|
+
=> HoboFields::EmailAddress
|
206
|
+
>> a.contact_address.validate
|
207
|
+
=> "is not valid"
|
208
|
+
|
209
|
+
But normally that method would be called for us during validation:
|
210
|
+
|
211
|
+
>> a.valid?
|
212
|
+
=> false
|
213
|
+
>> a.errors.full_messages
|
214
|
+
=> ["Contact address is not valid"]
|
215
|
+
>> a.contact_address = "me@me.com"
|
216
|
+
>> a.valid?
|
217
|
+
=> true
|
218
|
+
|
219
|
+
You can add this capability to your own rich types just by defining `#validate`
|
220
|
+
|
221
|
+
### Validating virtual fields
|
222
|
+
|
223
|
+
You can set the type of a virtual field to a rich type, e.g.
|
224
|
+
|
225
|
+
>>
|
226
|
+
class Advert
|
227
|
+
attr_accessor :alternative_email, :type => :email_address
|
228
|
+
end
|
229
|
+
|
230
|
+
By default, virtual fields are not subject to validation.
|
231
|
+
|
232
|
+
>> a = Advert.new :alternative_email => "woot!"
|
233
|
+
>> a.valid?
|
234
|
+
=> true
|
235
|
+
|
236
|
+
To have them validated use `validate_virtual_field`:
|
237
|
+
|
238
|
+
>>
|
239
|
+
class Advert
|
240
|
+
validate_virtual_field :alternative_email
|
241
|
+
end
|
242
|
+
>> a.valid?
|
243
|
+
=> false
|
244
|
+
|
245
|
+
## Cleanup
|
246
|
+
|
247
|
+
>> system "mysqladmin --force drop #{mysql_database}"
|
@@ -0,0 +1,410 @@
|
|
1
|
+
# HoboFields - Migration Generator
|
2
|
+
|
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
|
+
|
5
|
+
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
|
6
|
+
|
7
|
+
>> require 'rubygems'
|
8
|
+
>> require 'activesupport'
|
9
|
+
>> Dependencies.load_paths << '.'
|
10
|
+
>> Dependencies.mechanism = :require
|
11
|
+
|
12
|
+
And we'll require:
|
13
|
+
|
14
|
+
>> require 'activerecord'
|
15
|
+
>> require 'hobofields'
|
16
|
+
|
17
|
+
We also need to get ActiveRecord set up with a database connection
|
18
|
+
|
19
|
+
>> mysql_database = "hobofields_doctest"
|
20
|
+
>> system("mysqladmin create #{mysql_database}") or raise "could not create database"
|
21
|
+
>> ActiveRecord::Base.establish_connection(:adapter => "mysql",
|
22
|
+
:database => mysql_database,
|
23
|
+
:host => "localhost")
|
24
|
+
|
25
|
+
OK we're ready to get going.
|
26
|
+
|
27
|
+
|
28
|
+
## The migration generator -- introduction
|
29
|
+
|
30
|
+
The migration generator works by:
|
31
|
+
|
32
|
+
* Loading all of the models in your Rails app
|
33
|
+
* Using the Rails schema-dumper to extract information about the current state of the database.
|
34
|
+
* Calculating the changes that are required to bring the database into sync with your application.
|
35
|
+
|
36
|
+
Normally you would run the migration generator as a regular Rails generator. You would type
|
37
|
+
|
38
|
+
$ script/generator hobo_migration
|
39
|
+
|
40
|
+
in your Rails app, and the migration file would be created in `db/migrate`.
|
41
|
+
|
42
|
+
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.
|
43
|
+
|
44
|
+
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
|
+
>> HoboFields::MigrationGenerator.run
|
47
|
+
=> ["", ""]
|
48
|
+
|
49
|
+
|
50
|
+
### Models without `fields do` are ignored
|
51
|
+
|
52
|
+
The migration generator only takes into account classes that use HoboFields, i.e. classes with a `fields do` declaration. Models without this are ignored:
|
53
|
+
|
54
|
+
>> class Advert < ActiveRecord::Base; end
|
55
|
+
>> HoboFields::MigrationGenerator.run
|
56
|
+
=> ["", ""]
|
57
|
+
|
58
|
+
|
59
|
+
### Create the table
|
60
|
+
|
61
|
+
Here we see a simple `create_table` migration along with the `drop_table` down migration
|
62
|
+
|
63
|
+
>>
|
64
|
+
class Advert < ActiveRecord::Base
|
65
|
+
fields do
|
66
|
+
name :string
|
67
|
+
end
|
68
|
+
end
|
69
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
70
|
+
>> up
|
71
|
+
=>""
|
72
|
+
create_table :adverts do |t|
|
73
|
+
t.string :name
|
74
|
+
end
|
75
|
+
>> down
|
76
|
+
=> "drop_table :adverts"
|
77
|
+
|
78
|
+
Normally we would run the generated migration with `rake db:create`. We can achieve the same effect directly in Ruby like this:
|
79
|
+
|
80
|
+
>> ActiveRecord::Migration.class_eval up
|
81
|
+
>> Advert.columns.*.name
|
82
|
+
=> ["id", "name"]
|
83
|
+
|
84
|
+
We'll define a method to make that easier next time
|
85
|
+
|
86
|
+
>>
|
87
|
+
def migrate(renames={})
|
88
|
+
up, down = HoboFields::MigrationGenerator.run(renames)
|
89
|
+
puts up
|
90
|
+
ActiveRecord::Migration.class_eval(up)
|
91
|
+
ActiveRecord::Base.send(:subclasses).each { |model| model.reset_column_information }
|
92
|
+
[up, down]
|
93
|
+
end
|
94
|
+
|
95
|
+
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.
|
96
|
+
|
97
|
+
|
98
|
+
### Add fields
|
99
|
+
|
100
|
+
If we add a new field to the model, the migration generator will add it to the database.
|
101
|
+
|
102
|
+
>>
|
103
|
+
class Advert
|
104
|
+
fields do
|
105
|
+
name :string
|
106
|
+
body :text
|
107
|
+
published_at :datetime
|
108
|
+
end
|
109
|
+
end
|
110
|
+
>> up, down = migrate
|
111
|
+
>> up
|
112
|
+
=>""
|
113
|
+
add_column :adverts, :body, :text
|
114
|
+
add_column :adverts, :published_at, :datetime
|
115
|
+
>> down
|
116
|
+
=>""
|
117
|
+
remove_column :adverts, :body
|
118
|
+
remove_column :adverts, :published_at
|
119
|
+
>>
|
120
|
+
|
121
|
+
### Remove fields
|
122
|
+
|
123
|
+
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
|
124
|
+
|
125
|
+
>> Advert.field_specs.clear # not normally needed
|
126
|
+
class Advert < ActiveRecord::Base
|
127
|
+
fields do
|
128
|
+
name :string
|
129
|
+
body :text
|
130
|
+
end
|
131
|
+
end
|
132
|
+
>> up, down = migrate
|
133
|
+
>> up
|
134
|
+
=> "remove_column :adverts, :published_at"
|
135
|
+
>> down
|
136
|
+
=> "add_column :adverts, :published_at, :datetime"
|
137
|
+
|
138
|
+
### Rename a field
|
139
|
+
|
140
|
+
Here we rename the `name` field to `title`. By default the generator sees this as removing `name` and adding `title`.
|
141
|
+
|
142
|
+
>> Advert.field_specs.clear # not normally needed
|
143
|
+
class Advert < ActiveRecord::Base
|
144
|
+
fields do
|
145
|
+
title :string
|
146
|
+
body :text
|
147
|
+
end
|
148
|
+
end
|
149
|
+
>> # Just generate - don't run the migration:
|
150
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
151
|
+
>> up
|
152
|
+
=>""
|
153
|
+
add_column :adverts, :title, :string
|
154
|
+
remove_column :adverts, :name
|
155
|
+
>> down
|
156
|
+
=>""
|
157
|
+
remove_column :adverts, :title
|
158
|
+
add_column :adverts, :name, :string
|
159
|
+
>>
|
160
|
+
|
161
|
+
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
|
+
>> up, down = HoboFields::MigrationGenerator.run(:adverts => { :name => :title })
|
164
|
+
>> up
|
165
|
+
=> "rename_column :adverts, :name, :title"
|
166
|
+
>> down
|
167
|
+
=> "rename_column :adverts, :title, :name"
|
168
|
+
|
169
|
+
Let's apply that change to the database
|
170
|
+
|
171
|
+
>> migrate
|
172
|
+
|
173
|
+
|
174
|
+
### Change a type
|
175
|
+
|
176
|
+
>> Advert.attr_type :title
|
177
|
+
=> String
|
178
|
+
>>
|
179
|
+
class Advert
|
180
|
+
fields do
|
181
|
+
title :text
|
182
|
+
body :text
|
183
|
+
end
|
184
|
+
end
|
185
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
186
|
+
>> up
|
187
|
+
=> "change_column :adverts, :title, :text"
|
188
|
+
>> down
|
189
|
+
=> "change_column :adverts, :title, :string"
|
190
|
+
|
191
|
+
|
192
|
+
### Add a default
|
193
|
+
|
194
|
+
>>
|
195
|
+
class Advert
|
196
|
+
fields do
|
197
|
+
title :string, :default => "Untitled"
|
198
|
+
body :text
|
199
|
+
end
|
200
|
+
end
|
201
|
+
>> up, down = migrate
|
202
|
+
>> up
|
203
|
+
=> 'change_column :adverts, :title, :string, :default => "Untitled"'
|
204
|
+
>> down
|
205
|
+
=> "change_column :adverts, :title, :string"
|
206
|
+
|
207
|
+
|
208
|
+
### Foreign Keys
|
209
|
+
|
210
|
+
HoboFields extends the `belongs_to` macro so that it also declares the foreign-key field.
|
211
|
+
|
212
|
+
>>
|
213
|
+
class Advert
|
214
|
+
belongs_to :category
|
215
|
+
end
|
216
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
217
|
+
>> up
|
218
|
+
=> 'add_column :adverts, :category_id, :integer'
|
219
|
+
>> down
|
220
|
+
=> 'remove_column :adverts, :category_id'
|
221
|
+
|
222
|
+
Cleanup:
|
223
|
+
|
224
|
+
>> Advert.field_specs.delete(:category_id)
|
225
|
+
|
226
|
+
If you specify a custom foreign key, the migration generator observes that:
|
227
|
+
|
228
|
+
>>
|
229
|
+
class Advert
|
230
|
+
belongs_to :category, :foreign_key => "c_id"
|
231
|
+
end
|
232
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
233
|
+
>> up
|
234
|
+
=> 'add_column :adverts, :c_id, :integer'
|
235
|
+
>> down
|
236
|
+
=> 'remove_column :adverts, :c_id'
|
237
|
+
|
238
|
+
Cleanup:
|
239
|
+
|
240
|
+
>> Advert.field_specs.delete(:c_id)
|
241
|
+
|
242
|
+
|
243
|
+
### Timestamps
|
244
|
+
|
245
|
+
`updated_at` and `created_at` can be declared with the shorthand `timestamps`
|
246
|
+
|
247
|
+
>>
|
248
|
+
class Advert
|
249
|
+
fields do
|
250
|
+
timestamps
|
251
|
+
end
|
252
|
+
end
|
253
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
254
|
+
>> up
|
255
|
+
=>""
|
256
|
+
add_column :adverts, :created_at, :datetime
|
257
|
+
add_column :adverts, :updated_at, :datetime
|
258
|
+
>> down
|
259
|
+
=>""
|
260
|
+
remove_column :adverts, :created_at
|
261
|
+
remove_column :adverts, :updated_at
|
262
|
+
>>
|
263
|
+
|
264
|
+
Cleanup:
|
265
|
+
|
266
|
+
>> Advert.field_specs.delete(:updated_at)
|
267
|
+
>> Advert.field_specs.delete(:created_at)
|
268
|
+
|
269
|
+
|
270
|
+
### Rename a table
|
271
|
+
|
272
|
+
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.
|
273
|
+
|
274
|
+
>>
|
275
|
+
class Advert
|
276
|
+
set_table_name "ads"
|
277
|
+
fields do
|
278
|
+
title :string, :default => "Untitled"
|
279
|
+
body :text
|
280
|
+
end
|
281
|
+
end
|
282
|
+
>> up, down = HoboFields::MigrationGenerator.run(:adverts => :ads)
|
283
|
+
>> up
|
284
|
+
=> "rename_table :adverts, :ads"
|
285
|
+
>> down
|
286
|
+
=> "rename_table :ads, :adverts"
|
287
|
+
|
288
|
+
Set the table name back to what it should be and confirm we're in sync:
|
289
|
+
|
290
|
+
>> class Advert; set_table_name "adverts"; end
|
291
|
+
>> HoboFields::MigrationGenerator.run
|
292
|
+
=> ["", ""]
|
293
|
+
|
294
|
+
### Drop a table
|
295
|
+
|
296
|
+
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
|
+
|
298
|
+
>> HoboFields::MigrationGenerator.ignore_models = [ :advert ]
|
299
|
+
|
300
|
+
Dropping tables is where the automatic down-migration really comes in handy:
|
301
|
+
|
302
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
303
|
+
>> up
|
304
|
+
=> "drop_table :adverts"
|
305
|
+
>> down
|
306
|
+
=>""
|
307
|
+
create_table "adverts", :force => true do |t|
|
308
|
+
t.text "body"
|
309
|
+
t.string "title", :default => "Untitled"
|
310
|
+
end
|
311
|
+
>>
|
312
|
+
|
313
|
+
### Rename a table
|
314
|
+
|
315
|
+
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.
|
316
|
+
|
317
|
+
>>
|
318
|
+
class Advertisement < ActiveRecord::Base
|
319
|
+
fields do
|
320
|
+
title :string, :default => "Untitled"
|
321
|
+
body :text
|
322
|
+
end
|
323
|
+
end
|
324
|
+
>> up, down = HoboFields::MigrationGenerator.run(:adverts => :advertisements)
|
325
|
+
>> up
|
326
|
+
=> "rename_table :adverts, :advertisements"
|
327
|
+
>> down
|
328
|
+
=> "rename_table :advertisements, :adverts"
|
329
|
+
|
330
|
+
Now that we've seen the renaming we'll switch the 'ignore' setting to ignore that 'Advertisements' class.
|
331
|
+
|
332
|
+
>> HoboFields::MigrationGenerator.ignore_models = [ :advertisement ]
|
333
|
+
|
334
|
+
## STI
|
335
|
+
|
336
|
+
### Adding an STI subclass
|
337
|
+
|
338
|
+
Adding a subclass should introduce the 'type' column and no other changes
|
339
|
+
|
340
|
+
>>
|
341
|
+
class FancyAdvert < Advert
|
342
|
+
end
|
343
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
344
|
+
>> up
|
345
|
+
=> "add_column :adverts, :type, :string"
|
346
|
+
>> down
|
347
|
+
=> "remove_column :adverts, :type"
|
348
|
+
|
349
|
+
Cleanup
|
350
|
+
|
351
|
+
>> Advert.field_specs.delete(:type)
|
352
|
+
|
353
|
+
|
354
|
+
## Coping with multiple changes
|
355
|
+
|
356
|
+
The migration generator is designed to create complete migrations even if many changes to the models have taken place.
|
357
|
+
|
358
|
+
First let's confirm we're in a known state. One model, 'Advert', with a string 'title' and text 'body':
|
359
|
+
|
360
|
+
>> Advert.connection.tables
|
361
|
+
=> ["adverts"]
|
362
|
+
>> Advert.columns.*.name
|
363
|
+
=> ["id", "body", "title"]
|
364
|
+
>> HoboFields::MigrationGenerator.run
|
365
|
+
=> ["", ""]
|
366
|
+
|
367
|
+
|
368
|
+
### Rename a column and change the default
|
369
|
+
|
370
|
+
>> Advert.field_specs.clear
|
371
|
+
>>
|
372
|
+
class Advert
|
373
|
+
fields do
|
374
|
+
name :string, :default => "No Name"
|
375
|
+
body :text
|
376
|
+
end
|
377
|
+
end
|
378
|
+
>> up, down = HoboFields::MigrationGenerator.run(:adverts => {:title => :name})
|
379
|
+
>> up
|
380
|
+
=>""
|
381
|
+
rename_column :adverts, :title, :name
|
382
|
+
change_column :adverts, :name, :string, :default => "No Name"
|
383
|
+
>> down
|
384
|
+
=>""
|
385
|
+
rename_column :adverts, :name, :title
|
386
|
+
change_column :adverts, :title, :string, :default => "Untitled"
|
387
|
+
>>
|
388
|
+
|
389
|
+
|
390
|
+
### Rename a table and add a column
|
391
|
+
|
392
|
+
>> HoboFields::MigrationGenerator.ignore_models << :advert
|
393
|
+
class Ad < ActiveRecord::Base
|
394
|
+
fields do
|
395
|
+
title :string, :default => "Untitled"
|
396
|
+
body :text
|
397
|
+
created_at :datetime
|
398
|
+
end
|
399
|
+
end
|
400
|
+
>> up, down = HoboFields::MigrationGenerator.run(:adverts => :ads)
|
401
|
+
>> up
|
402
|
+
=>""
|
403
|
+
rename_table :adverts, :ads
|
404
|
+
|
405
|
+
add_column :ads, :created_at, :datetime
|
406
|
+
>>
|
407
|
+
|
408
|
+
## Cleanup
|
409
|
+
|
410
|
+
>> system "mysqladmin --force drop #{mysql_database}"
|