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 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 = ENV['RUBY'] || (defined?(JRUBY_VERSION) ? 'jruby' : '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
- begin
28
- require 'jeweler'
29
- Jeweler::Tasks.new do |gemspec|
30
- gemspec.version = HoboFields::VERSION
31
- gemspec.name = "hobofields"
32
- gemspec.email = "tom@tomlocke.com"
33
- gemspec.summary = "Rich field types and migration generator for Rails"
34
- gemspec.homepage = "http://hobocentral.net/"
35
- gemspec.authors = ["Tom Locke"]
36
- gemspec.rubyforge_project = "hobo"
37
- gemspec.add_dependency("rails", [">= 2.2.2"])
38
- gemspec.add_dependency("hobosupport", ["= #{HoboFields::VERSION}"])
39
- end
40
- Jeweler::GemcutterTasks.new
41
- Jeweler::RubyforgeTasks.new do |rubyforge|
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.0"
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.index(type) || ALIAS_TYPES[type]
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 include it once
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).where.descends_from_active_record?.reject {|c| c.name.starts_with?("CGI::") }
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).map { |a| a.options[:join_table] }
70
- end.flatten.compact.*.to_s
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.map &it.to_s.underscore
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 < HoboFields::ModelExtensions && m.name.underscore.not_in?(ignore_model_names) }
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 - habtm_tables
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.index(t) || model.table_name
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
- primary_key_option = ", :primary_key => :#{model.primary_key}" if model.primary_key != "id"
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 - [model.primary_key.to_sym]
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 + index_changes) * "\n",
296
- (undo_renames + undo_adds + undo_removes + undo_changes + undo_index_changes) * "\n"]
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) unless index_specs.*.fields.include?([inheritance_column])
29
+ index(inheritance_column)
26
30
  super
27
31
  end
28
32
 
29
33
  def self.index(fields, options = {})
30
- index_specs << IndexSpec.new(self, fields, options)
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".in?(type_class.public_instance_methods)
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
- if rename_to_choices.empty?
77
- puts "\nCONFIRM DROP! #{kind_str} #{name_prefix}#{t}"
78
- resp = input("Enter 'drop #{t}' to confirm:")
79
- if resp.strip != "drop " + t.to_s
80
- to_drop.delete(t)
81
- end
82
- else
83
- puts "\nDROP or RENAME?: #{kind_str} #{name_prefix}#{t}"
84
- puts "Rename choices: #{to_create * ', '}"
85
- resp = input("Enter either 'drop #{t}' or one of the rename choices:")
86
- resp.strip!
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
- resp.gsub!(' ', '_')
92
- to_drop.delete(t)
93
- if resp.in?(rename_to_choices)
94
- to_rename[t] = resp
95
- to_create.delete(resp)
96
- rename_to_choices.delete(resp)
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", "Dont' prompt for a migration name - just pick one") { |v| options[:default_name] = true }
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")[1]
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")[1]
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")[1]
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")[1]
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")[1]
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")[1]
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
@@ -1,6 +1,6 @@
1
1
  # HoboFields -- Rich Types
2
2
 
3
- This doctest describes the rich types bundles with HoboFields, and the process by which you can create and register your own 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.0
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-11-17 00:00:00 +00:00
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.0
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