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
data/lib/hobo_fields.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'hobosupport'
|
2
2
|
|
3
|
-
Dependencies.load_paths |= [ File.dirname(__FILE__) ]
|
3
|
+
Dependencies.load_paths |= [ File.dirname(__FILE__) ]
|
4
4
|
|
5
5
|
module Hobo
|
6
6
|
# Empty class to represent the boolean type.
|
@@ -8,21 +8,22 @@ module Hobo
|
|
8
8
|
end
|
9
9
|
|
10
10
|
module HoboFields
|
11
|
-
|
12
|
-
VERSION = "0.
|
13
|
-
|
11
|
+
|
12
|
+
VERSION = "0.8"
|
13
|
+
|
14
14
|
extend self
|
15
|
-
|
16
|
-
PLAIN_TYPES = {
|
15
|
+
|
16
|
+
PLAIN_TYPES = {
|
17
17
|
:boolean => Hobo::Boolean,
|
18
18
|
:date => Date,
|
19
|
-
:datetime => Time,
|
19
|
+
:datetime => (defined?(ActiveSupport::TimeWithZone) ? ActiveSupport::TimeWithZone : Time),
|
20
20
|
:integer => Fixnum,
|
21
21
|
:big_integer => BigDecimal,
|
22
|
+
:decimal => BigDecimal,
|
22
23
|
:float => Float,
|
23
24
|
:string => String
|
24
25
|
}
|
25
|
-
|
26
|
+
|
26
27
|
# Provide a lookup for these rather than loading them all preemptively
|
27
28
|
STANDARD_TYPES = {
|
28
29
|
:html => "HtmlString",
|
@@ -32,12 +33,12 @@ module HoboFields
|
|
32
33
|
:text => "Text",
|
33
34
|
:email_address => "EmailAddress"
|
34
35
|
}
|
35
|
-
|
36
|
+
|
36
37
|
@field_types = HashWithIndifferentAccess.new(PLAIN_TYPES)
|
37
38
|
@never_wrap_types = Set.new([NilClass, Hobo::Boolean, TrueClass, FalseClass])
|
38
39
|
|
39
40
|
attr_reader :field_types
|
40
|
-
|
41
|
+
|
41
42
|
def to_class(type)
|
42
43
|
if type.is_a?(Symbol, String)
|
43
44
|
type = type.to_sym
|
@@ -46,51 +47,51 @@ module HoboFields
|
|
46
47
|
type # assume it's already a class
|
47
48
|
end
|
48
49
|
end
|
49
|
-
|
50
|
-
|
50
|
+
|
51
|
+
|
51
52
|
def to_name(type)
|
52
53
|
field_types.index(type)
|
53
54
|
end
|
54
55
|
|
55
|
-
|
56
|
+
|
56
57
|
def can_wrap?(val)
|
57
58
|
# Make sure we get the *real* class
|
58
59
|
klass = Object.instance_method(:class).bind(val).call
|
59
60
|
!@never_wrap_types.any? { |c| klass <= c }
|
60
61
|
end
|
61
|
-
|
62
|
-
|
62
|
+
|
63
|
+
|
63
64
|
def never_wrap(type)
|
64
65
|
@never_wrap_types << type
|
65
66
|
end
|
66
|
-
|
67
|
-
|
67
|
+
|
68
|
+
|
68
69
|
def register_type(name, klass)
|
69
70
|
field_types[name] = klass
|
70
71
|
end
|
71
|
-
|
72
|
-
|
72
|
+
|
73
|
+
|
73
74
|
def plain_type?(type_name)
|
74
75
|
type_name.in?(PLAIN_TYPES)
|
75
76
|
end
|
76
|
-
|
77
|
-
|
77
|
+
|
78
|
+
|
78
79
|
def standard_class(name)
|
79
80
|
class_name = STANDARD_TYPES[name]
|
80
81
|
"HoboFields::#{class_name}".constantize if class_name
|
81
82
|
end
|
82
|
-
|
83
|
+
|
83
84
|
def enable
|
84
85
|
require "hobo_fields/enum_string"
|
85
86
|
require "hobo_fields/fields_declaration"
|
86
87
|
|
87
88
|
# Add the fields do declaration to ActiveRecord::Base
|
88
89
|
ActiveRecord::Base.send(:include, HoboFields::FieldsDeclaration)
|
89
|
-
|
90
|
+
|
90
91
|
# Monkey patch ActiveRecord so that the attribute read & write methods
|
91
92
|
# automatically wrap richly-typed fields.
|
92
|
-
ActiveRecord::AttributeMethods::ClassMethods.class_eval do
|
93
|
-
|
93
|
+
ActiveRecord::AttributeMethods::ClassMethods.class_eval do
|
94
|
+
|
94
95
|
# Define an attribute reader method. Cope with nil column.
|
95
96
|
def define_read_method(symbol, attr_name, column)
|
96
97
|
cast_code = column.type_cast_code('v') if column
|
@@ -110,11 +111,11 @@ module HoboFields
|
|
110
111
|
else
|
111
112
|
access_code
|
112
113
|
end
|
113
|
-
|
114
|
-
evaluate_attribute_method(attr_name,
|
114
|
+
|
115
|
+
evaluate_attribute_method(attr_name,
|
115
116
|
"def #{symbol}; @attributes_cache['#{attr_name}'] ||= begin; #{src}; end; end")
|
116
117
|
end
|
117
|
-
|
118
|
+
|
118
119
|
def define_write_method(attr_name)
|
119
120
|
src = if connected? && (type_wrapper = try.attr_type(attr_name)) &&
|
120
121
|
type_wrapper.is_a?(Class) && type_wrapper.not_in?(HoboFields::PLAIN_TYPES.values)
|
@@ -125,14 +126,14 @@ module HoboFields
|
|
125
126
|
end
|
126
127
|
evaluate_attribute_method(attr_name,
|
127
128
|
"def #{attr_name}=(val); write_attribute('#{attr_name}', #{src});end", "#{attr_name}=")
|
128
|
-
|
129
|
+
|
129
130
|
end
|
130
131
|
|
131
132
|
end
|
132
|
-
|
133
|
-
|
133
|
+
|
134
|
+
|
134
135
|
end
|
135
|
-
|
136
|
+
|
136
137
|
end
|
137
138
|
|
138
139
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../lib/hobofields'
|
1
2
|
class HoboMigrationGenerator < Rails::Generator::Base
|
2
3
|
|
3
4
|
def initialize(runtime_args, runtime_options = {})
|
@@ -7,46 +8,63 @@ class HoboMigrationGenerator < Rails::Generator::Base
|
|
7
8
|
"hobo_migration_#{i+1}"
|
8
9
|
end
|
9
10
|
end
|
10
|
-
|
11
|
+
|
12
|
+
def migrations_pending?
|
13
|
+
pending_migrations = ActiveRecord::Migrator.new(:up, 'db/migrate').pending_migrations
|
14
|
+
|
15
|
+
if pending_migrations.any?
|
16
|
+
puts "You have #{pending_migrations.size} pending migration#{'s' if pending_migrations.size > 1}:"
|
17
|
+
pending_migrations.each do |pending_migration|
|
18
|
+
puts ' %4d %s' % [pending_migration.version, pending_migration.name]
|
19
|
+
end
|
20
|
+
true
|
21
|
+
else
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
11
27
|
def manifest
|
28
|
+
return record {|m| } if migrations_pending?
|
29
|
+
|
12
30
|
generator = HoboFields::MigrationGenerator.new(self)
|
13
31
|
up, down = generator.generate
|
14
32
|
|
15
33
|
if up.blank?
|
16
34
|
puts "Database and models match -- nothing to change"
|
17
|
-
return record {|m| }
|
35
|
+
return record {|m| }
|
18
36
|
end
|
19
|
-
|
37
|
+
|
20
38
|
puts "\n---------- Up Migration ----------", up, "----------------------------------"
|
21
39
|
puts "\n---------- Down Migration --------", down, "----------------------------------"
|
22
|
-
|
23
|
-
action = input("What now: [g]enerate migration, generate and [m]igrate now or [c]ancel?",
|
40
|
+
|
41
|
+
action = input("What now: [g]enerate migration, generate and [m]igrate now or [c]ancel?", /^(g|m|c)$/)
|
24
42
|
|
25
43
|
if action == 'c'
|
26
44
|
# record nothing to keep the generator happy
|
27
45
|
record {|m| }
|
28
46
|
else
|
29
47
|
puts "\nMigration filename:", "(you can type spaces instead of '_' -- every little helps)"
|
30
|
-
migration_name = input("Filename [#@migration_name]:").strip.gsub(' ', '_')
|
48
|
+
migration_name = input("Filename [#@migration_name]:", /^[a-z0-9_ ]/).strip.gsub(' ', '_')
|
31
49
|
migration_name = @migration_name if migration_name.blank?
|
32
|
-
|
33
|
-
at_exit {
|
34
|
-
|
50
|
+
|
51
|
+
at_exit { rake_migrate } if action == 'm'
|
52
|
+
|
35
53
|
up.gsub!("\n", "\n ")
|
36
54
|
down.gsub!("\n", "\n ")
|
37
55
|
|
38
56
|
record do |m|
|
39
|
-
m.migration_template 'migration.rb', 'db/migrate',
|
40
|
-
:assigns => { :up => up, :down => down, :migration_name => migration_name.camelize },
|
57
|
+
m.migration_template 'migration.rb', 'db/migrate',
|
58
|
+
:assigns => { :up => up, :down => down, :migration_name => migration_name.camelize },
|
41
59
|
:migration_file_name => migration_name
|
42
60
|
end
|
43
61
|
end
|
44
62
|
rescue HoboFields::FieldSpec::UnknownSqlTypeError => e
|
45
|
-
puts "Invalid field type
|
63
|
+
puts "Invalid field type: #{e}"
|
46
64
|
record {|m| }
|
47
65
|
end
|
48
|
-
|
49
|
-
|
66
|
+
|
67
|
+
|
50
68
|
def extract_renames!(to_create, to_drop, kind_str, name_prefix="")
|
51
69
|
to_rename = {}
|
52
70
|
rename_to_choices = to_create
|
@@ -62,7 +80,7 @@ class HoboMigrationGenerator < Rails::Generator::Base
|
|
62
80
|
puts "Rename choices: #{to_create * ', '}"
|
63
81
|
resp = input("Enter either 'drop #{t}' or one of the rename choices:")
|
64
82
|
resp.strip!
|
65
|
-
|
83
|
+
|
66
84
|
if resp == "drop " + t
|
67
85
|
# Leave things as they are
|
68
86
|
else
|
@@ -78,11 +96,11 @@ class HoboMigrationGenerator < Rails::Generator::Base
|
|
78
96
|
to_rename
|
79
97
|
end
|
80
98
|
|
81
|
-
|
82
|
-
def input(prompt,
|
99
|
+
|
100
|
+
def input(prompt, format=nil)
|
83
101
|
print(prompt + " ")
|
84
|
-
if
|
85
|
-
while
|
102
|
+
if format
|
103
|
+
while (response = STDIN.readline.strip.downcase) !~ format
|
86
104
|
print(prompt + " ")
|
87
105
|
end
|
88
106
|
response
|
@@ -90,6 +108,14 @@ class HoboMigrationGenerator < Rails::Generator::Base
|
|
90
108
|
STDIN.readline
|
91
109
|
end
|
92
110
|
end
|
93
|
-
|
111
|
+
|
112
|
+
def rake_migrate
|
113
|
+
if RUBY_PLATFORM =~ /mswin32/
|
114
|
+
system "rake.bat db:migrate"
|
115
|
+
else
|
116
|
+
system "rake db:migrate"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
94
120
|
end
|
95
121
|
|
File without changes
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Description:
|
2
|
+
Stubs out a new model. Pass the model name, either CamelCased or
|
3
|
+
under_scored, and an optional list of attribute pairs as arguments.
|
4
|
+
|
5
|
+
Attribute pairs are column_name:sql_type arguments specifying the
|
6
|
+
model's attributes. Timestamps are added by default, so you don't have to
|
7
|
+
specify them by hand as 'created_at:datetime updated_at:datetime'.
|
8
|
+
|
9
|
+
You don't have to think up every attribute up front, but it helps to
|
10
|
+
sketch out a few so you can start working with the model immediately.
|
11
|
+
|
12
|
+
This generates a model class in app/models, a unit test in test/unit,
|
13
|
+
a test fixture in test/fixtures/singular_name.yml, and a migration in
|
14
|
+
db/migrate.
|
15
|
+
|
16
|
+
Examples:
|
17
|
+
`./script/generate hobofield_model account`
|
18
|
+
|
19
|
+
creates an Account model, test, fixture, and migration:
|
20
|
+
Model: app/models/account.rb
|
21
|
+
Test: test/unit/account_test.rb
|
22
|
+
Fixtures: test/fixtures/accounts.yml
|
23
|
+
|
24
|
+
`./script/generate hobofield_model post title:string body:text published:boolean`
|
25
|
+
|
26
|
+
creates a Post model with a string title, text body, and published flag.
|
27
|
+
|
28
|
+
After the model is created, and the fields are specified, use hobofield
|
29
|
+
to create the migrations incrementally.
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class HobofieldModelGenerator < Rails::Generator::NamedBase
|
2
|
+
default_options :skip_timestamps => false, :skip_fixture => false
|
3
|
+
|
4
|
+
def manifest
|
5
|
+
record do |m|
|
6
|
+
# Check for class naming collisions.
|
7
|
+
m.class_collisions class_path, class_name, "#{class_name}Test"
|
8
|
+
|
9
|
+
# Model, test, and fixture directories.
|
10
|
+
m.directory File.join('app/models', class_path)
|
11
|
+
m.directory File.join('test/unit', class_path)
|
12
|
+
m.directory File.join('test/fixtures', class_path)
|
13
|
+
|
14
|
+
# Create stubs
|
15
|
+
m.template "model.rb.erb", "app/models/#{file_name}.rb"
|
16
|
+
m.template "test.rb.erb", "test/unit/#{file_name}_test.rb"
|
17
|
+
|
18
|
+
unless options[:skip_fixture]
|
19
|
+
m.template 'fixtures.yml.erb', File.join('test/fixtures', "#{file_name.pluralize}.yml")
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
def banner
|
27
|
+
"Usage: #{$0} #{spec.name} ModelName [field:type, field:type]"
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_options!(opt)
|
31
|
+
opt.separator ''
|
32
|
+
opt.separator 'Options:'
|
33
|
+
opt.on("--skip-timestamps",
|
34
|
+
"Don't add timestamps to the migration file for this model") { |v| options[:skip_timestamps] = v }
|
35
|
+
opt.on("--skip-fixture",
|
36
|
+
"Don't generation a fixture file for this model") { |v| options[:skip_fixture] = v}
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
|
2
|
+
|
3
|
+
<% unless attributes.empty? -%>
|
4
|
+
one:
|
5
|
+
<% for attribute in attributes -%>
|
6
|
+
<%= attribute.name %>: <%= attribute.default %>
|
7
|
+
<% end -%>
|
8
|
+
|
9
|
+
two:
|
10
|
+
<% for attribute in attributes -%>
|
11
|
+
<%= attribute.name %>: <%= attribute.default %>
|
12
|
+
<% end -%>
|
13
|
+
<% else -%>
|
14
|
+
# one:
|
15
|
+
# column: value
|
16
|
+
#
|
17
|
+
# two:
|
18
|
+
# column: value
|
19
|
+
<% end -%>
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
data/test/hobofields.rdoctest
CHANGED
@@ -1,57 +1,94 @@
|
|
1
1
|
# HoboFields
|
2
2
|
|
3
|
-
##
|
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.
|
3
|
+
## Introduction
|
6
4
|
|
7
|
-
|
5
|
+
Welcome to HoboFields -- a spin-off from the Hobo project (Hobo not required!).
|
8
6
|
|
9
|
-
|
7
|
+
**HoboFields writes your Rails migrations for you! Your migration writing days are over!**
|
10
8
|
|
11
|
-
HoboFields
|
9
|
+
All we ask is that you declare your fields in the model. It's still perfectly DRY because you're not having to repeat that in the migration -- HoboFields does that for you. In fact, you'll come to love having them there.
|
12
10
|
|
13
|
-
|
11
|
+
It still has all the benefits of writing your own migrations, for example if you want to add some special code to migrate your old data, you can just edit the generated migration.
|
14
12
|
|
15
|
-
|
13
|
+
## Example
|
16
14
|
|
17
|
-
|
15
|
+
First off, if you're using the migration generator outside of Hobo, do remember the `--skip-migration` option when generating your models:
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
HoboFields provides two main features:
|
17
|
+
$ ./script/generate model blog_post --skip-migration
|
22
18
|
|
23
|
-
|
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
|
19
|
+
Now edit your model as follows:
|
27
20
|
|
28
21
|
class BlogPost < ActiveRecord::Base
|
29
22
|
fields do
|
30
23
|
title :string
|
31
24
|
body :text
|
25
|
+
timestamps
|
32
26
|
end
|
33
27
|
end
|
34
28
|
{: .ruby}
|
35
29
|
|
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
30
|
|
38
|
-
|
31
|
+
Then, simply run
|
32
|
+
|
33
|
+
$ ./script/genearte hobo_migration
|
34
|
+
|
35
|
+
And voila
|
36
|
+
|
37
|
+
---------- Up Migration ----------
|
38
|
+
create_table :blog_posts do |t|
|
39
|
+
t.string :title
|
40
|
+
t.text :body
|
41
|
+
t.datetime :created_at
|
42
|
+
t.datetime :updated_at
|
43
|
+
end
|
44
|
+
----------------------------------
|
45
|
+
|
46
|
+
---------- Down Migration --------
|
47
|
+
drop_table :blog_posts
|
48
|
+
----------------------------------
|
49
|
+
{: .ruby}
|
50
|
+
|
51
|
+
The migration generator has created 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. You are now free to simply hack away on your app and run the migration generator every time the database needs to play catch-up.
|
52
|
+
|
53
|
+
Note that the migration generator is interactive -- it can't tell the difference between renaming something vs. adding one thing and removing another, so sometimes it will ask you to clarify. It's a bit picky about what it makes you type in response, because we really don't want you to lose data when someone's amazing twitter distracts you at the wrong moment.
|
54
|
+
|
55
|
+
## Installing
|
56
|
+
|
57
|
+
The simplest and recommended way to install HoboFeilds is as a gem:
|
39
58
|
|
59
|
+
$ gem install hobofields
|
40
60
|
|
41
|
-
|
61
|
+
The source lives on GitHub as part of the main Hobo repo:
|
42
62
|
|
43
|
-
|
63
|
+
- [http://github.com/tablatom/hobo](http://github.com/tablatom/hobo)
|
44
64
|
|
45
|
-
|
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.
|
65
|
+
The simplest way to install HoboFields as a plugin is to use the subversion mirror:
|
48
66
|
|
49
|
-
|
67
|
+
svn export svn://hobocentral.net/hobo/tags/rel_0.7.5/hobofields vendor/plugins
|
50
68
|
|
51
|
-
##
|
69
|
+
## Rich Types
|
52
70
|
|
53
|
-
|
71
|
+
In addition to the migration generator, HoboFields provides a Rich-Type mechanism that allows you to declare your fields using higher level concepts like "email-address" or "markdown text". This can give you both automatic validations, and automatic rendering of those values in the UI (this works particularly well if you use the rest of Hobo).
|
54
72
|
|
55
|
-
|
73
|
+
Read more:
|
74
|
+
|
75
|
+
- [HoboFields Rich Types](/hobofields/rich_types)
|
76
|
+
|
77
|
+
## API
|
78
|
+
|
79
|
+
HoboFields provides various API methods. Read more:
|
80
|
+
|
81
|
+
- [HoboFields API](/hobofields/hobofields_api)
|
82
|
+
|
83
|
+
## Migration Generator Details
|
84
|
+
|
85
|
+
The migration generator doctests 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
|
86
|
+
|
87
|
+
- [Migration Generator Details](/hobofields/migration_generator)
|
88
|
+
|
89
|
+
## About Doctests
|
90
|
+
|
91
|
+
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.
|
92
|
+
|
93
|
+
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!
|
56
94
|
|
57
|
-
Documentation for the full set of rich types bundled with HoboFields, and how to create your own, is [here](/hobofields/rich_types).
|