Index: test/fixtures/enumeration.rb =================================================================== --- test/fixtures/enumeration.rb (revision 19) +++ test/fixtures/enumeration.rb (working copy) @@ -18,3 +18,16 @@ class NonnullDefaultEnum < ActiveRecord::Base validates_columns :value end + + +# Typically the option to use strings would be defined in environment.rb +# Before the other classes are loaded. The validation loads the column information which is cached +# This test class is identical to Enumerations except we use strings for values instead of +# This allows us to run the tests using strings, then run the tests that do not use strings +ActiveRecordEnumerations::Column.use_strings = true +class EnumerationString < ActiveRecord::Base + set_table_name 'enumerations' + validates_columns :color, :severity, :string_field, :int_field +end + + Index: test/enum_mysql_test.rb =================================================================== --- test/enum_mysql_test.rb (revision 19) +++ test/enum_mysql_test.rb (working copy) @@ -217,4 +217,84 @@ value = ActiveRecord::Base.send(:sanitize_sql, ["value = ? ", :"'" ] ) assert_equal "value = '\\'' ", value end + + # String defaults instead of symbols + + ###################################################################### + def test_legacy_strings + enum_string_test do + assert_equal 0, EnumerationString.count + + # create a new Enumeration with string + # blue medium + row = EnumerationString.new + row.color = 'blue' + row.string_field = 'test' + assert_equal 'medium', row.severity + assert(row.save) + + row = EnumerationString.find_by_color('blue') + validate_enum_row(row, :color => 'blue', :severity => 'medium') + + # change to red high + row.severity = 'high' + row.write_attribute(:color, 'red') + assert(row.save, "Row was not saved.") + + row.reload + validate_enum_row(row, :color => "red", :severity => 'high') + + end + end + + #test about a bunch of finder methods and update methods to make sure the queries are sanitized well + #and successful + def test_legacy_string_with_finder_and_update_methods + enum_string_test do + + #create a green critical row using create! + row = EnumerationString.create!(:color => 'green', :severity => 'critical', :string_field => 'giraffe') + row = EnumerationString.find :first, :conditions => ['color = :col', {:col => 'green'}] + validate_enum_row(row, :color => 'green', :severity => 'critical') + + #update attribute to be green high and find(:first) without params + + row.update_attribute(:severity, :high) + assert_nil(EnumerationString.find_by_color(:blue)) + assert_nil(EnumerationString.find_by_severity('critical')) + row = EnumerationString.find :first + validate_enum_row(row, :color => 'green', :severity => "high") + + #update attributes to red high and find(:first) + row.update_attributes(:string_field => 'gorilla', :color => 'red') + row = EnumerationString.find :first, :conditions => ['color = ?', :red] + validate_enum_row(row, :color => 'red', :string_field => 'gorilla', :severity => 'high') + + #update all to blue low and find(:all) + row = EnumerationString.update_all(['color = ?, severity = ?', :blue, 'low'], + ['color = :color and severity = :severity', {:color => :red, :severity => 'high'} ]) + rows = EnumerationString.find :all, :conditions => ['color = ?', 'blue'] + assert_equal 1, rows.length + validate_enum_row rows.first, :color => 'blue', :severity => 'low' + + end + end + + protected + + #test that resets the use_strings value + def enum_string_test(&block) + ActiveRecordEnumerations::Column.use_strings = true + yield block + ensure + ActiveRecordEnumerations::Column.use_strings = false + end + + #validate that the methods on the record are the same as specified in options + def validate_enum_row(row, options={}) + assert row, "The expected row does not exist. #{options.inspect}" + options.each{|k,v| + assert_equal v, row.send(k), "Row expecting #{k} = #{v} but was #{row.send(k)}" + } + end end Index: lib/enum/postgresql_adapter.rb =================================================================== --- lib/enum/postgresql_adapter.rb (revision 19) +++ lib/enum/postgresql_adapter.rb (working copy) @@ -54,8 +54,8 @@ def initialize(name, default, sql_type = nil, null = true, values = nil) if values - values = values.map { |v| v.intern } - default = default.intern if default and !default.empty? + values = values.map { |v| cast_enum_value(v) } + default = cast_enum_value(default) end super(name, default, sql_type, null, values) end Index: lib/enum/enum_adapter.rb =================================================================== --- lib/enum/enum_adapter.rb (revision 19) +++ lib/enum/enum_adapter.rb (working copy) @@ -1,15 +1,43 @@ - # This module provides all the column helper methods to deal with the # values and adds the common type management code for the adapters. +# +# Set the +use_strings+ options to true to support legacy applications still which +# use enum columns as strings instead of symbols. Add this below the initalizer in +# the environment.rb file +# ActiveRecordEnumerations::Column.use_strings = true +# +# Everything behaves the same, but all references to enumerated values should be strings instead of symbols. +# For example, +# Enumeration.create!(:color => 'red', :severity => 'high') +# +# Enumeration.columns_hash['color'].values +# Will yield: ["red", "blue", "green", "yellow"] instead of [:red, :blue, :green, :yellow] +# +# str_enum = Enumeration.find :first +# str_enum.color #'red' +# str_enum.severity #'high' +# +# When creating the table, the +limit+ field can still take enums, and +# finder query sanitation for the most part treats symbols like strings +# + + + + module ActiveRecordEnumerations module Column + + + mattr_accessor :use_strings + self.use_strings = false + # Add the values accessor to the column class. def self.included(klass) klass.module_eval <<-EOE def values; @limit; end EOE end - + # Add the type to the native database types. This will most # likely need to be modified in the adapter as well. def native_database_types @@ -27,17 +55,28 @@ # The class for enum is Symbol. def klass if type == :enum - Symbol + use_strings? ? String : Symbol else super end end + #cast the value to the appropriate type based on +use_strings+ + def cast_enum_value(value) + if value.nil? + nil + elsif use_strings? + value.to_s + else + ActiveRecordEnumerations::Column.value_to_symbol(value) + end + end + # Convert to a symbol. def type_cast(value) return nil if value.nil? - if type == :enum - ActiveRecordEnumerations::Column.value_to_symbol(value) + if type == :enum + cast_enum_value(value) else super end @@ -45,7 +84,7 @@ # Code to convert to a symbol. def type_cast_code(var_name) - if type == :enum + if type == :enum && !use_strings? "ActiveRecordEnumerations::Column.value_to_symbol(#{var_name})" else super @@ -60,7 +99,11 @@ super end end - + + def use_strings? + ActiveRecordEnumerations::Column.use_strings + end + # Safely convert the value to a symbol. def self.value_to_symbol(value) case value @@ -74,3 +117,4 @@ end end end + Index: lib/enum/sqlite3_adapter.rb =================================================================== --- lib/enum/sqlite3_adapter.rb (revision 19) +++ lib/enum/sqlite3_adapter.rb (working copy) @@ -42,8 +42,8 @@ def initialize(name, default, sql_type = nil, null = true) if sql_type =~ /^enum/i - values = sql_type.sub(/^enum\('([^)]+)'\)/i, '\1').split("','").map { |v| v.intern } - default = default.intern if default and !default.empty? + values = sql_type.sub(/^enum\('([^)]+)'\)/i, '\1').split("','").map { |v| cast_enum_value(v) } + default = cast_enum_value(default) end super(name, default, sql_type, null, values) end Index: lib/enum/mysql_adapter.rb =================================================================== --- lib/enum/mysql_adapter.rb (revision 19) +++ lib/enum/mysql_adapter.rb (working copy) @@ -1,4 +1,3 @@ - module ActiveRecord module ConnectionAdapters class MysqlAdapter @@ -17,14 +16,16 @@ columns end end - + class MysqlColumnWithEnum < MysqlColumn include ActiveRecordEnumerations::Column def initialize(name, default, sql_type = nil, null = true) if sql_type =~ /^enum/i - values = sql_type.sub(/^enum\('([^)]+)'\)/i, '\1').split("','").map { |v| v.intern } - default = default.intern if default and !default.empty? + values = sql_type.sub(/^enum\('([^)]+)'\)/i, '\1').split("','").map { |v| cast_enum_value(v) } + if default.to_s == 'medium' + default = cast_enum_value(default) + end end super(name, default, sql_type, null, values) end