hobofields 0.9.0 → 0.9.100
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +16 -20
- data/lib/hobo_fields.rb +11 -2
- data/lib/hobo_fields/enum_string.rb +43 -2
- data/lib/hobo_fields/field_spec.rb +2 -1
- data/lib/hobo_fields/fields_declaration.rb +5 -3
- data/lib/hobo_fields/index_spec.rb +2 -2
- data/lib/hobo_fields/lifecycle_state.rb +14 -0
- data/lib/hobo_fields/migration_generator.rb +76 -19
- data/lib/hobo_fields/model_extensions.rb +16 -4
- data/rails_generators/hobo_migration/hobo_migration_generator.rb +36 -21
- data/test/migration_generator.rdoctest +17 -6
- data/test/rich_types.rdoctest +44 -4
- metadata +4 -3
data/Rakefile
CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
|
|
2
2
|
require 'activerecord'
|
3
3
|
ActiveRecord::ActiveRecordError # hack for https://rails.lighthouseapp.com/projects/8994/tickets/2577-when-using-activerecordassociations-outside-of-rails-a-nameerror-is-thrown
|
4
4
|
|
5
|
-
RUBY =
|
5
|
+
RUBY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']).sub(/.*\s.*/m, '"\&"')
|
6
6
|
RUBYDOCTEST = ENV['RUBYDOCTEST'] || "#{RUBY} `which rubydoctest`"
|
7
7
|
|
8
8
|
$:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '/../hobofields/lib')
|
@@ -24,23 +24,19 @@ namespace "test" do
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
rubyforge.doc_task = false
|
43
|
-
end
|
44
|
-
rescue LoadError
|
45
|
-
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
27
|
+
require 'jeweler'
|
28
|
+
Jeweler::Tasks.new do |gemspec|
|
29
|
+
gemspec.version = HoboFields::VERSION
|
30
|
+
gemspec.name = "hobofields"
|
31
|
+
gemspec.email = "tom@tomlocke.com"
|
32
|
+
gemspec.summary = "Rich field types and migration generator for Rails"
|
33
|
+
gemspec.homepage = "http://hobocentral.net/"
|
34
|
+
gemspec.authors = ["Tom Locke"]
|
35
|
+
gemspec.rubyforge_project = "hobo"
|
36
|
+
gemspec.add_dependency("rails", [">= 2.2.2"])
|
37
|
+
gemspec.add_dependency("hobosupport", ["= #{HoboFields::VERSION}"])
|
38
|
+
end
|
39
|
+
Jeweler::GemcutterTasks.new
|
40
|
+
Jeweler::RubyforgeTasks.new do |rubyforge|
|
41
|
+
rubyforge.doc_task = false
|
46
42
|
end
|
data/lib/hobo_fields.rb
CHANGED
@@ -9,7 +9,7 @@ end
|
|
9
9
|
|
10
10
|
module HoboFields
|
11
11
|
|
12
|
-
VERSION = "0.9.
|
12
|
+
VERSION = "0.9.100"
|
13
13
|
|
14
14
|
extend self
|
15
15
|
|
@@ -60,7 +60,7 @@ module HoboFields
|
|
60
60
|
|
61
61
|
|
62
62
|
def to_name(type)
|
63
|
-
field_types.
|
63
|
+
field_types.key(type) || ALIAS_TYPES[type]
|
64
64
|
end
|
65
65
|
|
66
66
|
|
@@ -100,6 +100,15 @@ module HoboFields
|
|
100
100
|
# Add the fields do declaration to ActiveRecord::Base
|
101
101
|
ActiveRecord::Base.send(:include, HoboFields::FieldsDeclaration)
|
102
102
|
|
103
|
+
# automatically load other rich types from app/rich_types/*.rb
|
104
|
+
# don't assume we're in a Rails app
|
105
|
+
if defined?(::Rails)
|
106
|
+
Dir[File.join(::Rails.root, 'app', 'rich_types', '*.rb')].each do |f|
|
107
|
+
# TODO: should we complain if field_types doesn't get a new value? Might be useful to warn people if they're missing a register_type
|
108
|
+
require f
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
103
112
|
# Monkey patch ActiveRecord so that the attribute read & write methods
|
104
113
|
# automatically wrap richly-typed fields.
|
105
114
|
ActiveRecord::AttributeMethods::ClassMethods.class_eval do
|
@@ -19,16 +19,40 @@ module HoboFields
|
|
19
19
|
|
20
20
|
def with_values(*values)
|
21
21
|
@values = values.*.to_s
|
22
|
+
@translated_values = Hash.new do |hash, value|
|
23
|
+
if name.blank? || value.blank?
|
24
|
+
hash[value] = value
|
25
|
+
else
|
26
|
+
hash[value] = I18n.t("#{name.tableize}.#{value}", :default => value)
|
27
|
+
@detranslated_values[hash[value]] = value
|
28
|
+
hash[value]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@detranslated_values = Hash.new do |hash, value|
|
33
|
+
if name.blank?
|
34
|
+
hash[value] = value
|
35
|
+
else
|
36
|
+
hash[value] = @values.detect(proc { value } ) {|v|
|
37
|
+
@translated_values[v]==value
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
22
41
|
end
|
23
42
|
|
24
43
|
attr_accessor :values
|
25
44
|
|
45
|
+
attr_accessor :translated_values
|
46
|
+
|
47
|
+
attr_accessor :detranslated_values
|
48
|
+
|
26
49
|
def for(*values)
|
50
|
+
options = values.extract_options!
|
27
51
|
values = values.*.to_s
|
28
52
|
c = Class.new(EnumString) do
|
29
53
|
values.each do |v|
|
30
54
|
const_name = v.upcase.gsub(/[^a-z0-9_]/i, '_').gsub(/_+/, '_')
|
31
|
-
const_name = "V" + const_name if const_name =~ /^[0-9_]/
|
55
|
+
const_name = "V" + const_name if const_name =~ /^[0-9_]/ || const_name.blank?
|
32
56
|
const_set(const_name, self.new(v)) unless const_defined?(const_name)
|
33
57
|
|
34
58
|
method_name = "is_#{v.underscore}?"
|
@@ -36,6 +60,7 @@ module HoboFields
|
|
36
60
|
end
|
37
61
|
end
|
38
62
|
c.with_values(*values)
|
63
|
+
c.set_name(options[:name]) if options[:name]
|
39
64
|
c
|
40
65
|
end
|
41
66
|
|
@@ -44,12 +69,28 @@ module HoboFields
|
|
44
69
|
end
|
45
70
|
alias_method :to_s, :inspect
|
46
71
|
|
72
|
+
def set_name(name)
|
73
|
+
@name = name
|
74
|
+
end
|
75
|
+
|
76
|
+
def name
|
77
|
+
@name || super
|
78
|
+
end
|
79
|
+
|
47
80
|
end
|
48
81
|
|
49
82
|
COLUMN_TYPE = :string
|
50
83
|
|
84
|
+
def initialize(value)
|
85
|
+
super(self.class.detranslated_values.nil? ? value : self.class.detranslated_values[value.to_s])
|
86
|
+
end
|
87
|
+
|
51
88
|
def validate
|
52
|
-
"must be one of #{self.class.values * ', '}" unless self.in?(self.class.values)
|
89
|
+
"must be one of #{self.class.values.map{|v| v.blank? ? '\'\'' : v} * ', '}" unless self.in?(self.class.values)
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_html(xmldoctype = true)
|
93
|
+
self.class.translated_values[self]
|
53
94
|
end
|
54
95
|
|
55
96
|
def ==(other)
|
@@ -9,8 +9,9 @@ module HoboFields
|
|
9
9
|
self.model = model
|
10
10
|
self.name = name.to_sym
|
11
11
|
self.type = type.is_a?(String) ? type.to_sym : type
|
12
|
+
position = options.delete(:position)
|
12
13
|
self.options = options
|
13
|
-
self.position = model.field_specs.length
|
14
|
+
self.position = position || model.field_specs.length
|
14
15
|
end
|
15
16
|
|
16
17
|
attr_accessor :model, :name, :type, :position, :options
|
@@ -2,13 +2,15 @@ module HoboFields
|
|
2
2
|
|
3
3
|
FieldsDeclaration = classy_module do
|
4
4
|
|
5
|
-
def self.fields(&b)
|
5
|
+
def self.fields(include_in_migration = true, &b)
|
6
6
|
# Any model that calls 'fields' gets a bunch of other
|
7
|
-
# functionality included automatically, but make sure we only
|
7
|
+
# functionality included automatically, but make sure we only
|
8
|
+
# include it once
|
8
9
|
include HoboFields::ModelExtensions unless HoboFields::ModelExtensions.in?(included_modules)
|
10
|
+
@include_in_migration ||= include_in_migration
|
9
11
|
|
10
12
|
if b
|
11
|
-
dsl = FieldDeclarationDsl.new(self)
|
13
|
+
dsl = HoboFields::FieldDeclarationDsl.new(self)
|
12
14
|
if b.arity == 1
|
13
15
|
yield dsl
|
14
16
|
else
|
@@ -16,8 +16,8 @@ module HoboFields
|
|
16
16
|
def self.for_model(model, old_table_name=nil)
|
17
17
|
t = old_table_name || model.table_name
|
18
18
|
model.connection.indexes(t).map do |i|
|
19
|
-
self.new(model, i.columns, :name => i.name, :unique => i.unique, :table_name => old_table_name)
|
20
|
-
end
|
19
|
+
self.new(model, i.columns, :name => i.name, :unique => i.unique, :table_name => old_table_name) unless model.ignore_indexes.include?(i.name)
|
20
|
+
end.compact
|
21
21
|
end
|
22
22
|
|
23
23
|
def default_name?
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module HoboFields
|
2
|
+
class LifecycleState < String
|
3
|
+
|
4
|
+
COLUMN_TYPE = :string
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :table_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_html(xmldoctype = true)
|
11
|
+
I18n.t("#{self.class.table_name}.states.#{self}", :default => self)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -2,6 +2,42 @@ module HoboFields
|
|
2
2
|
|
3
3
|
class MigrationGeneratorError < RuntimeError; end
|
4
4
|
|
5
|
+
class HabtmModelShim < Struct.new(:join_table, :foreign_keys, :connection)
|
6
|
+
|
7
|
+
def self.from_reflection(refl)
|
8
|
+
result = self.new
|
9
|
+
result.join_table = refl.options[:join_table].to_s
|
10
|
+
result.foreign_keys = [refl.primary_key_name.to_s, refl.association_foreign_key.to_s].sort
|
11
|
+
# this may fail in weird ways if HABTM is running across two DB connections (assuming that's even supported)
|
12
|
+
# figure that anybody who sets THAT up can deal with their own migrations...
|
13
|
+
result.connection = refl.active_record.connection
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
def table_name
|
18
|
+
self.join_table
|
19
|
+
end
|
20
|
+
|
21
|
+
def field_specs
|
22
|
+
i = 0
|
23
|
+
foreign_keys.inject({}) do |h, v|
|
24
|
+
# some trickery to avoid an infinite loop when FieldSpec#initialize tries to call model.field_specs
|
25
|
+
h[v] = FieldSpec.new(self, v, :integer, :position => i)
|
26
|
+
i += 1
|
27
|
+
h
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def primary_key
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def index_specs
|
36
|
+
[]
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
5
41
|
class MigrationGenerator
|
6
42
|
|
7
43
|
@ignore_models = []
|
@@ -40,7 +76,7 @@ module HoboFields
|
|
40
76
|
# ActiveRecord::Base, excluding anything in the CGI module
|
41
77
|
def table_model_classes
|
42
78
|
load_rails_models
|
43
|
-
ActiveRecord::Base.send(:subclasses).
|
79
|
+
ActiveRecord::Base.send(:subclasses).reject {|c| (c.base_class != c) || c.name.starts_with?("CGI::") }
|
44
80
|
end
|
45
81
|
|
46
82
|
|
@@ -65,19 +101,23 @@ module HoboFields
|
|
65
101
|
|
66
102
|
# list habtm join tables
|
67
103
|
def habtm_tables
|
104
|
+
reflections = Hash.new { |h, k| h[k] = Array.new }
|
68
105
|
ActiveRecord::Base.send(:subclasses).map do |c|
|
69
|
-
c.reflect_on_all_associations(:has_and_belongs_to_many).
|
70
|
-
|
106
|
+
c.reflect_on_all_associations(:has_and_belongs_to_many).each do |a|
|
107
|
+
reflections[a.options[:join_table].to_s] << a
|
108
|
+
end
|
109
|
+
end
|
110
|
+
reflections
|
71
111
|
end
|
72
112
|
|
73
113
|
# Returns an array of model classes and an array of table names
|
74
114
|
# that generation needs to take into account
|
75
115
|
def models_and_tables
|
76
|
-
ignore_model_names = MigrationGenerator.ignore_models
|
116
|
+
ignore_model_names = MigrationGenerator.ignore_models.*.to_s.*.underscore
|
77
117
|
all_models = table_model_classes
|
78
|
-
hobo_models = all_models.select { |m| m
|
118
|
+
hobo_models = all_models.select { |m| m.try.include_in_migration && m.name.underscore.not_in?(ignore_model_names) }
|
79
119
|
non_hobo_models = all_models - hobo_models
|
80
|
-
db_tables = connection.tables - MigrationGenerator.ignore_tables.*.to_s - non_hobo_models.*.table_name
|
120
|
+
db_tables = connection.tables - MigrationGenerator.ignore_tables.*.to_s - non_hobo_models.*.table_name
|
81
121
|
[hobo_models, db_tables]
|
82
122
|
end
|
83
123
|
|
@@ -134,8 +174,8 @@ module HoboFields
|
|
134
174
|
raise MigrationGeneratorError, "Unable to resolve migration ambiguities in table #{table_name}"
|
135
175
|
end
|
136
176
|
end
|
137
|
-
|
138
|
-
|
177
|
+
|
178
|
+
|
139
179
|
def always_ignore_tables
|
140
180
|
# TODO: figure out how to do this in a sane way and be compatible with 2.2 and 2.3 - class has moved
|
141
181
|
sessions_table = CGI::Session::ActiveRecordStore::Session.table_name if
|
@@ -158,6 +198,10 @@ module HoboFields
|
|
158
198
|
models_by_table_name[m.table_name] = m
|
159
199
|
end
|
160
200
|
end
|
201
|
+
# generate shims for HABTM models
|
202
|
+
habtm_tables.each do |name, refls|
|
203
|
+
models_by_table_name[name] = HabtmModelShim.from_reflection(refls.first)
|
204
|
+
end
|
161
205
|
model_table_names = models_by_table_name.keys
|
162
206
|
|
163
207
|
to_create = model_table_names - db_tables
|
@@ -189,25 +233,35 @@ module HoboFields
|
|
189
233
|
|
190
234
|
changes = []
|
191
235
|
undo_changes = []
|
236
|
+
index_changes = []
|
237
|
+
undo_index_changes = []
|
192
238
|
to_change.each do |t|
|
193
239
|
model = models_by_table_name[t]
|
194
|
-
table = to_rename.
|
240
|
+
table = to_rename.key(t) || model.table_name
|
195
241
|
if table.in?(db_tables)
|
196
|
-
change, undo = change_table(model, table)
|
242
|
+
change, undo, index_change, undo_index = change_table(model, table)
|
197
243
|
changes << change
|
198
244
|
undo_changes << undo
|
245
|
+
index_changes << index_change
|
246
|
+
undo_index_changes << undo_index
|
199
247
|
end
|
200
248
|
end
|
201
249
|
|
202
|
-
up = [renames, drops, creates, changes].flatten.reject(&:blank?) * "\n\n"
|
203
|
-
down = [undo_changes, undo_renames, undo_drops, undo_creates].flatten.reject(&:blank?) * "\n\n"
|
250
|
+
up = [renames, drops, creates, changes, index_changes].flatten.reject(&:blank?) * "\n\n"
|
251
|
+
down = [undo_changes, undo_renames, undo_drops, undo_creates, undo_index_changes].flatten.reject(&:blank?) * "\n\n"
|
204
252
|
|
205
253
|
[up, down]
|
206
254
|
end
|
207
255
|
|
208
256
|
def create_table(model)
|
209
257
|
longest_field_name = model.field_specs.values.map { |f| f.sql_type.to_s.length }.max
|
210
|
-
|
258
|
+
if model.primary_key != "id"
|
259
|
+
if model.primary_key
|
260
|
+
primary_key_option = ", :primary_key => :#{model.primary_key}"
|
261
|
+
else
|
262
|
+
primary_key_option = ", :id => false"
|
263
|
+
end
|
264
|
+
end
|
211
265
|
(["create_table :#{model.table_name}#{primary_key_option} do |t|"] +
|
212
266
|
model.field_specs.values.sort_by{|f| f.position}.map {|f| create_field(f, longest_field_name)} +
|
213
267
|
["end"] + (MigrationGenerator.disable_indexing ? [] : create_indexes(model))) * "\n"
|
@@ -226,15 +280,16 @@ module HoboFields
|
|
226
280
|
new_table_name = model.table_name
|
227
281
|
|
228
282
|
db_columns = model.connection.columns(current_table_name).index_by{|c|c.name}
|
229
|
-
key_missing = db_columns[model.primary_key].nil?
|
283
|
+
key_missing = db_columns[model.primary_key].nil? && model.primary_key
|
230
284
|
db_columns -= [model.primary_key]
|
231
285
|
|
232
286
|
model_column_names = model.field_specs.keys.*.to_s
|
233
287
|
db_column_names = db_columns.keys.*.to_s
|
234
288
|
|
235
289
|
to_add = model_column_names - db_column_names
|
236
|
-
to_add += [model.primary_key] if key_missing
|
237
|
-
to_remove = db_column_names - model_column_names
|
290
|
+
to_add += [model.primary_key] if key_missing && model.primary_key
|
291
|
+
to_remove = db_column_names - model_column_names
|
292
|
+
to_remove = to_remove - [model.primary_key.to_sym] if model.primary_key
|
238
293
|
|
239
294
|
to_rename = extract_column_renames!(to_add, to_remove, new_table_name)
|
240
295
|
|
@@ -292,12 +347,14 @@ module HoboFields
|
|
292
347
|
|
293
348
|
index_changes, undo_index_changes = change_indexes(model, current_table_name)
|
294
349
|
|
295
|
-
[(renames + adds + removes + changes
|
296
|
-
(undo_renames + undo_adds + undo_removes + undo_changes
|
350
|
+
[(renames + adds + removes + changes) * "\n",
|
351
|
+
(undo_renames + undo_adds + undo_removes + undo_changes) * "\n",
|
352
|
+
index_changes * "\n",
|
353
|
+
undo_index_changes * "\n"]
|
297
354
|
end
|
298
355
|
|
299
356
|
def change_indexes(model, old_table_name)
|
300
|
-
return [[],[]] if MigrationGenerator.disable_indexing
|
357
|
+
return [[],[]] if MigrationGenerator.disable_indexing || model.is_a?(HabtmModelShim)
|
301
358
|
new_table_name = model.table_name
|
302
359
|
existing_indexes = IndexSpec.for_model(model, old_table_name)
|
303
360
|
model_indexes = model.index_specs
|
@@ -2,6 +2,9 @@ module HoboFields
|
|
2
2
|
|
3
3
|
ModelExtensions = classy_module do
|
4
4
|
|
5
|
+
# ignore the model in the migration until somebody sets
|
6
|
+
# @include_in_migration via the fields declaration
|
7
|
+
inheriting_cattr_reader :include_in_migration => false
|
5
8
|
|
6
9
|
# attr_types holds the type class for any attribute reader (i.e. getter
|
7
10
|
# method) that returns rich-types
|
@@ -17,19 +20,26 @@ module HoboFields
|
|
17
20
|
|
18
21
|
# index_specs holds IndexSpec objects for all the declared indexes.
|
19
22
|
inheriting_cattr_reader :index_specs => []
|
23
|
+
inheriting_cattr_reader :ignore_indexes => []
|
20
24
|
|
21
25
|
def self.inherited(klass)
|
22
26
|
fields do |f|
|
23
27
|
f.field(inheritance_column, :string)
|
24
28
|
end
|
25
|
-
index(inheritance_column)
|
29
|
+
index(inheritance_column)
|
26
30
|
super
|
27
31
|
end
|
28
32
|
|
29
33
|
def self.index(fields, options = {})
|
30
|
-
|
34
|
+
# don't double-index fields
|
35
|
+
index_specs << HoboFields::IndexSpec.new(self, fields, options) unless index_specs.*.fields.include?(Array.wrap(fields).*.to_s)
|
31
36
|
end
|
32
37
|
|
38
|
+
# tell the migration generator to ignore the named index. Useful for existing indexes, or for indexes
|
39
|
+
# that can't be automatically generated (for example: an prefix index in MySQL)
|
40
|
+
def self.ignore_index(index_name)
|
41
|
+
ignore_indexes << index_name.to_s
|
42
|
+
end
|
33
43
|
|
34
44
|
private
|
35
45
|
|
@@ -49,7 +59,9 @@ module HoboFields
|
|
49
59
|
options = attrs.extract_options!
|
50
60
|
type = options.delete(:type)
|
51
61
|
attrs << options unless options.empty?
|
62
|
+
public
|
52
63
|
attr_accessor_without_rich_types(*attrs)
|
64
|
+
private
|
53
65
|
|
54
66
|
if type
|
55
67
|
type = HoboFields.to_class(type)
|
@@ -124,7 +136,7 @@ module HoboFields
|
|
124
136
|
add_validations_for_field(name, type, args)
|
125
137
|
add_index_for_field(name, args, options)
|
126
138
|
declare_attr_type(name, type, options) unless HoboFields.plain_type?(type)
|
127
|
-
field_specs[name] = FieldSpec.new(self, name, type, options)
|
139
|
+
field_specs[name] = HoboFields::FieldSpec.new(self, name, type, options)
|
128
140
|
attr_order << name unless name.in?(attr_order)
|
129
141
|
end
|
130
142
|
|
@@ -136,7 +148,7 @@ module HoboFields
|
|
136
148
|
validates_uniqueness_of name, :allow_nil => !:required.in?(args) if :unique.in?(args)
|
137
149
|
|
138
150
|
type_class = HoboFields.to_class(type)
|
139
|
-
if type_class && "validate"
|
151
|
+
if type_class && type_class.public_method_defined?("validate")
|
140
152
|
self.validate do |record|
|
141
153
|
v = record.send(name)._?.validate
|
142
154
|
record.errors.add(name, v) if v.is_a?(String)
|
@@ -53,7 +53,9 @@ class HoboMigrationGenerator < Rails::Generator::Base
|
|
53
53
|
at_exit { rake_migrate } if action == 'm'
|
54
54
|
|
55
55
|
up.gsub!("\n", "\n ")
|
56
|
+
up.gsub!(/ +\n/, "\n")
|
56
57
|
down.gsub!("\n", "\n ")
|
58
|
+
down.gsub!(/ +\n/, "\n")
|
57
59
|
|
58
60
|
record do |m|
|
59
61
|
m.migration_template 'migration.rb', 'db/migrate',
|
@@ -73,27 +75,40 @@ class HoboMigrationGenerator < Rails::Generator::Base
|
|
73
75
|
to_rename = {}
|
74
76
|
rename_to_choices = to_create
|
75
77
|
to_drop.dup.each do |t|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
if resp == "drop " + t
|
89
|
-
# Leave things as they are
|
78
|
+
while true
|
79
|
+
if rename_to_choices.empty?
|
80
|
+
puts "\nCONFIRM DROP! #{kind_str} #{name_prefix}#{t}"
|
81
|
+
resp = input("Enter 'drop #{t}' to confirm or press enter to keep:")
|
82
|
+
if resp.strip == "drop " + t.to_s
|
83
|
+
break
|
84
|
+
elsif resp.strip.empty?
|
85
|
+
to_drop.delete(t)
|
86
|
+
break
|
87
|
+
else
|
88
|
+
next
|
89
|
+
end
|
90
90
|
else
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
91
|
+
puts "\nDROP, RENAME or KEEP?: #{kind_str} #{name_prefix}#{t}"
|
92
|
+
puts "Rename choices: #{to_create * ', '}"
|
93
|
+
resp = input("Enter either 'drop #{t}' or one of the rename choices or press enter to keep:")
|
94
|
+
resp.strip!
|
95
|
+
|
96
|
+
if resp == "drop " + t
|
97
|
+
# Leave things as they are
|
98
|
+
break
|
99
|
+
else
|
100
|
+
resp.gsub!(' ', '_')
|
101
|
+
to_drop.delete(t)
|
102
|
+
if resp.in?(rename_to_choices)
|
103
|
+
to_rename[t] = resp
|
104
|
+
to_create.delete(resp)
|
105
|
+
rename_to_choices.delete(resp)
|
106
|
+
break
|
107
|
+
elsif resp.empty?
|
108
|
+
break
|
109
|
+
else
|
110
|
+
next
|
111
|
+
end
|
97
112
|
end
|
98
113
|
end
|
99
114
|
end
|
@@ -132,7 +147,7 @@ class HoboMigrationGenerator < Rails::Generator::Base
|
|
132
147
|
opt.separator 'Options:'
|
133
148
|
|
134
149
|
opt.on("--force-drop", "Don't prompt with 'drop or rename' - just drop everything") { |v| options[:force_drop] = true }
|
135
|
-
opt.on("--default-name", "
|
150
|
+
opt.on("--default-name", "Don't prompt for a migration name - just pick one") { |v| options[:default_name] = true }
|
136
151
|
opt.on("--generate", "Don't prompt for action - generate the migration") { |v| options[:action] = 'g' }
|
137
152
|
opt.on("--migrate", "Don't prompt for action - generate and migrate") { |v| options[:action] = 'm' }
|
138
153
|
end
|
@@ -70,6 +70,9 @@ The migration generator only takes into account classes that use HoboFields, i.e
|
|
70
70
|
>> HoboFields::MigrationGenerator.run
|
71
71
|
=> ["", ""]
|
72
72
|
|
73
|
+
You can also tell HoboFields to ignore additional tables. You can place this command in your environment.rb or elsewhere:
|
74
|
+
|
75
|
+
>> HoboFields::MigrationGenerator.ignore_tables = ["green_fishes"]
|
73
76
|
|
74
77
|
### Create the table
|
75
78
|
|
@@ -266,10 +269,12 @@ foreign-key field. It also generates an index on the field.
|
|
266
269
|
>> up
|
267
270
|
=>
|
268
271
|
"add_column :adverts, :category_id, :integer
|
272
|
+
|
269
273
|
add_index :adverts, [:category_id]"
|
270
274
|
>> down
|
271
275
|
=>
|
272
276
|
"remove_column :adverts, :category_id
|
277
|
+
|
273
278
|
remove_index :adverts, :name => :index_adverts_on_category_id"
|
274
279
|
|
275
280
|
Cleanup:
|
@@ -289,6 +294,7 @@ If you specify a custom foreign key, the migration generator observes that:
|
|
289
294
|
>> up
|
290
295
|
=>
|
291
296
|
"add_column :adverts, :c_id, :integer
|
297
|
+
|
292
298
|
add_index :adverts, [:c_id]"
|
293
299
|
|
294
300
|
Cleanup:
|
@@ -325,6 +331,7 @@ You can specify the index name with :index
|
|
325
331
|
>> up
|
326
332
|
=>
|
327
333
|
"add_column :adverts, :category_id, :integer
|
334
|
+
|
328
335
|
add_index :adverts, [:category_id], :name => 'my_index'"
|
329
336
|
|
330
337
|
Cleanup:
|
@@ -373,7 +380,7 @@ You can add an index to a field definition
|
|
373
380
|
end
|
374
381
|
end
|
375
382
|
>> up, down = HoboFields::MigrationGenerator.run
|
376
|
-
>> up.split("\n")[
|
383
|
+
>> up.split("\n")[2]
|
377
384
|
=> 'add_index :adverts, [:title]'
|
378
385
|
|
379
386
|
Cleanup:
|
@@ -391,7 +398,7 @@ You can ask for a unique index
|
|
391
398
|
end
|
392
399
|
end
|
393
400
|
>> up, down = HoboFields::MigrationGenerator.run
|
394
|
-
>> up.split("\n")[
|
401
|
+
>> up.split("\n")[2]
|
395
402
|
=> 'add_index :adverts, [:title], :unique => true'
|
396
403
|
|
397
404
|
Cleanup:
|
@@ -409,7 +416,7 @@ You can specify the name for the index
|
|
409
416
|
end
|
410
417
|
end
|
411
418
|
>> up, down = HoboFields::MigrationGenerator.run
|
412
|
-
>> up.split("\n")[
|
419
|
+
>> up.split("\n")[2]
|
413
420
|
=> "add_index :adverts, [:title], :name => 'my_index'"
|
414
421
|
|
415
422
|
Cleanup:
|
@@ -425,7 +432,7 @@ You can ask for an index outside of the fields block
|
|
425
432
|
index :title
|
426
433
|
end
|
427
434
|
>> up, down = HoboFields::MigrationGenerator.run
|
428
|
-
>> up.split("\n")[
|
435
|
+
>> up.split("\n")[2]
|
429
436
|
=> "add_index :adverts, [:title]"
|
430
437
|
|
431
438
|
Cleanup:
|
@@ -441,7 +448,7 @@ The available options for the index function are `:unique` and `:name`
|
|
441
448
|
index :title, :unique => true, :name => 'my_index'
|
442
449
|
end
|
443
450
|
>> up, down = HoboFields::MigrationGenerator.run
|
444
|
-
>> up.split("\n")[
|
451
|
+
>> up.split("\n")[2]
|
445
452
|
=> "add_index :adverts, [:title], :unique => true, :name => 'my_index'"
|
446
453
|
|
447
454
|
Cleanup:
|
@@ -457,7 +464,7 @@ You can create an index on more than one field
|
|
457
464
|
index [:title, :category_id]
|
458
465
|
end
|
459
466
|
>> up, down = HoboFields::MigrationGenerator.run
|
460
|
-
>> up.split("\n")[
|
467
|
+
>> up.split("\n")[2]
|
461
468
|
=> "add_index :adverts, [:title, :category_id]"
|
462
469
|
|
463
470
|
Cleanup:
|
@@ -466,6 +473,8 @@ Cleanup:
|
|
466
473
|
>> Advert.index_specs.delete_if {|spec| spec.fields==["title", "category_id"]}
|
467
474
|
{.hidden}
|
468
475
|
|
476
|
+
Finally, you can specify that the migration generator should completely ignore an index by passing its name to ignore_index in the model. This is helpful for preserving indices that can't be automatically generated, such as prefix indices in MySQL.
|
477
|
+
|
469
478
|
### Rename a table
|
470
479
|
|
471
480
|
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.
|
@@ -556,10 +565,12 @@ Adding a subclass or two should introduce the 'type' column and no other changes
|
|
556
565
|
>> up
|
557
566
|
=>
|
558
567
|
"add_column :adverts, :type, :string
|
568
|
+
|
559
569
|
add_index :adverts, [:type]"
|
560
570
|
>> down
|
561
571
|
=>
|
562
572
|
"remove_column :adverts, :type
|
573
|
+
|
563
574
|
remove_index :adverts, :name => :index_adverts_on_type"
|
564
575
|
|
565
576
|
Cleanup
|
data/test/rich_types.rdoctest
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# HoboFields -- Rich Types
|
2
2
|
|
3
|
-
This doctest describes the rich types
|
3
|
+
This doctest describes the rich types bundled with HoboFields, and the process by which you can create and register your own types.
|
4
4
|
|
5
5
|
Our test requires rails:
|
6
6
|
{.hidden}
|
@@ -41,7 +41,6 @@ Finally we can require hobofields:
|
|
41
41
|
>> require 'hobosupport'
|
42
42
|
>> require 'hobofields'
|
43
43
|
|
44
|
-
|
45
44
|
## `to_html` method
|
46
45
|
|
47
46
|
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.
|
@@ -163,7 +162,7 @@ Provides validation of correct email address format.
|
|
163
162
|
|
164
163
|
`HoboFields::EnumString` is not a rich type that you use directly. It's a "type generator", rather like Ruby's `Struct`. It's used for the common situation in database driven apps that you want an enumerated type, but it's not worth going to the extra bother of a separate table enumerating the values. For example you could create a type to represent the status of an article:
|
165
164
|
|
166
|
-
>> ArticleStatus = HoboFields::EnumString.for(:draft, :approved, :published)
|
165
|
+
>> ArticleStatus = HoboFields::EnumString.for('', :draft, :approved, :published)
|
167
166
|
=> ArticleStatus
|
168
167
|
|
169
168
|
Note that, like all dynamically created classes in Ruby, the class is anonymous until assigned to a constant:
|
@@ -208,6 +207,13 @@ Note that every enum you create is a subclass of HoboFields::EnumString:
|
|
208
207
|
>> a.is_a?(HoboFields::EnumString)
|
209
208
|
=> true
|
210
209
|
|
210
|
+
EnumString's have a `validate` function defined to fit in with the
|
211
|
+
ActiveRecord validation framework.
|
212
|
+
|
213
|
+
>> a.validate
|
214
|
+
=> nil
|
215
|
+
>> ArticleStatus.new("junked").validate
|
216
|
+
=> "must be one of '', draft, approved, published"
|
211
217
|
|
212
218
|
### Using EnumString in your models
|
213
219
|
|
@@ -228,12 +234,46 @@ Sometimes it's nice to have a proper type name. Here's one way you might go abou
|
|
228
234
|
class Article < ActiveRecord::Base
|
229
235
|
Status = HoboFields::EnumString.for(:draft, :approved, :published)
|
230
236
|
fields do
|
231
|
-
status Status
|
237
|
+
status Article::Status
|
232
238
|
end
|
233
239
|
end
|
234
240
|
>> Article.attr_type :status
|
235
241
|
=> Article::Status
|
236
242
|
|
243
|
+
### Translating EnumString's
|
244
|
+
|
245
|
+
Named EnumString's may be translated. Here is an example fr.yml:
|
246
|
+
|
247
|
+
fr:
|
248
|
+
article/statuses:
|
249
|
+
draft: "esquisse"
|
250
|
+
approved: "approuvé"
|
251
|
+
published: "publiés"
|
252
|
+
|
253
|
+
Alternatively, the translations may be supplied in ruby:
|
254
|
+
|
255
|
+
>> Article::Status.translated_values = { "draft"=>"esquisse", "approved"=>"approuvé", "published"=>"publiés" }
|
256
|
+
|
257
|
+
EnumString's can be created with the translated values. Internally,
|
258
|
+
they always store the native version.
|
259
|
+
|
260
|
+
>> a=Article::Status.new("esquisse")
|
261
|
+
=> "draft"
|
262
|
+
>> a.is_draft?
|
263
|
+
=> true
|
264
|
+
|
265
|
+
The translated value is available via `to_html`:
|
266
|
+
|
267
|
+
>> Article::Status::PUBLISHED.to_html
|
268
|
+
=> "publiés"
|
269
|
+
|
270
|
+
Translations only work with named EnumString's. The recommended way of naming the EnumString is to assign it to a constant, but if you do not wish to do this, you can supply the name in an option:
|
271
|
+
|
272
|
+
>> HoboFields::EnumString.for('one', 'two', 'three', :name => "Count").inspect
|
273
|
+
=> "Count"
|
274
|
+
|
275
|
+
`tableize` will be called on your name to provide the translation key.
|
276
|
+
|
237
277
|
## Cleanup
|
238
278
|
{.hidden}
|
239
279
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hobofields
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.100
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Locke
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-12-01 00:00:00 +00:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -30,7 +30,7 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.9.
|
33
|
+
version: 0.9.100
|
34
34
|
version:
|
35
35
|
description:
|
36
36
|
email: tom@tomlocke.com
|
@@ -55,6 +55,7 @@ files:
|
|
55
55
|
- lib/hobo_fields/fields_declaration.rb
|
56
56
|
- lib/hobo_fields/html_string.rb
|
57
57
|
- lib/hobo_fields/index_spec.rb
|
58
|
+
- lib/hobo_fields/lifecycle_state.rb
|
58
59
|
- lib/hobo_fields/markdown_string.rb
|
59
60
|
- lib/hobo_fields/migration_generator.rb
|
60
61
|
- lib/hobo_fields/model_extensions.rb
|