hobofields 0.9.0 → 0.9.100
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/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
|