Skip to content

Commit

Permalink
Working with all Rails version
Browse files Browse the repository at this point in the history
  • Loading branch information
FabioMR committed Jul 29, 2014
1 parent 640a2b2 commit d89041f
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 124 deletions.
2 changes: 1 addition & 1 deletion delocalize.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ Gem::Specification.new do |s|
s.test_files = Dir['test/**/*']

s.add_dependency 'minitest', '< 5'
s.add_dependency 'rails', '>= 4.0'
s.add_dependency 'rails', '>= 3.0'
s.add_development_dependency 'timecop', '~> 0.3.5'
end
41 changes: 4 additions & 37 deletions lib/delocalize/rails_ext/action_view.rb
Original file line number Diff line number Diff line change
@@ -1,38 +1,5 @@
require 'action_view'

# TODO: also override other methods like to_check_box_tag since they might contain numeric values?
# ActionView needs some patching too

ActionView::Helpers::Tags::TextField.class_eval do
include ActionView::Helpers::NumberHelper

def render_with_localization
if object && (@options[:value].blank? || !@options[:value].is_a?(String)) && object.respond_to?(:column_for_attribute) && column = object.column_for_attribute(@method_name)
value = @options[:value] || object.send(@method_name)

if column.number?
number_options = I18n.t(:'number.format')
separator = @options.delete(:separator) || number_options[:separator]
delimiter = @options.delete(:delimiter) || number_options[:delimiter]
precision = @options.delete(:precision) || number_options[:precision]
opts = { :separator => separator, :delimiter => delimiter, :precision => precision }
# integers don't need a precision
opts.merge!(:precision => 0) if column.type == :integer

hidden_for_integer = field_type == 'hidden' && column.type == :integer

# the number will be formatted only if it has no numericality errors
if object.respond_to?(:errors) && !Array(object.errors[@method_name]).try(:include?, 'is not a number')
# we don't format integer hidden fields because this breaks nested_attributes
@options[:value] = number_with_precision(value, opts) unless hidden_for_integer
end
elsif column.date? || column.time?
@options[:value] = value ? I18n.l(value, :format => @options.delete(:format)) : nil
end
end

render_without_localization
end

alias_method_chain :render, :localization
if Gem::Version.new(ActionPack::VERSION::STRING) >= Gem::Version.new('4.0.0.beta')
require 'delocalize/rails_ext/action_view_rails4'
else
require 'delocalize/rails_ext/action_view_rails3'
end
53 changes: 53 additions & 0 deletions lib/delocalize/rails_ext/action_view_rails3.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
require 'action_view'

# TODO: also override other methods like to_check_box_tag since they might contain numeric values?
# ActionView needs some patching too

ActionView::Helpers::InstanceTag.class_eval do
include ActionView::Helpers::NumberHelper

alias original_to_input_field_tag to_input_field_tag
def to_input_field_tag(field_type, options = {})
options.symbolize_keys!
# numbers and dates/times should be localized unless value is already defined
if object && (options[:value].blank? || !options[:value].is_a?(String)) && object.respond_to?(:column_for_attribute) && column = object.column_for_attribute(method_name)
value = options[:value] || object.send(method_name)

if column.number?
number_options = I18n.t(:'number.format')
separator = options.delete(:separator) || number_options[:separator]
delimiter = options.delete(:delimiter) || number_options[:delimiter]
precision = options.delete(:precision) || number_options[:precision]
opts = { :separator => separator, :delimiter => delimiter, :precision => precision }
# integers don't need a precision
opts.merge!(:precision => 0) if column.type == :integer

hidden_for_integer = field_type == 'hidden' && column.type == :integer

# the number will be formatted only if it has no numericality errors
if object.respond_to?(:errors) && !Array(object.errors[method_name]).try(:include?, 'is not a number')
# we don't format integer hidden fields because this breaks nested_attributes
options[:value] = number_with_precision(value, opts) unless hidden_for_integer
end
elsif column.date? || column.time?
options[:value] = value ? I18n.l(value, :format => options.delete(:format)) : nil
end
end

original_to_input_field_tag(field_type, options)
end
end

# TODO: does it make sense to also override FormTagHelper methods?
# ActionView::Helpers::FormTagHelper.class_eval do
# include ActionView::Helpers::NumberHelper
#
# alias original_text_field_tag text_field_tag
# def text_field_tag(name, value = nil, options = {})
# value = options.delete(:value) if options.key?(:value)
# if value.is_a?(Numeric)
# value = number_with_delimiter(value)
# end
# original_text_field_tag(name, value, options)
# end
# end
38 changes: 38 additions & 0 deletions lib/delocalize/rails_ext/action_view_rails4.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
require 'action_view'

# TODO: also override other methods like to_check_box_tag since they might contain numeric values?
# ActionView needs some patching too

ActionView::Helpers::Tags::TextField.class_eval do
include ActionView::Helpers::NumberHelper

def render_with_localization
if object && (@options[:value].blank? || !@options[:value].is_a?(String)) && object.respond_to?(:column_for_attribute) && column = object.column_for_attribute(@method_name)
value = @options[:value] || object.send(@method_name)

if column.number?
number_options = I18n.t(:'number.format')
separator = @options.delete(:separator) || number_options[:separator]
delimiter = @options.delete(:delimiter) || number_options[:delimiter]
precision = @options.delete(:precision) || number_options[:precision]
opts = { :separator => separator, :delimiter => delimiter, :precision => precision }
# integers don't need a precision
opts.merge!(:precision => 0) if column.type == :integer

hidden_for_integer = field_type == 'hidden' && column.type == :integer

# the number will be formatted only if it has no numericality errors
if object.respond_to?(:errors) && !Array(object.errors[@method_name]).try(:include?, 'is not a number')
# we don't format integer hidden fields because this breaks nested_attributes
@options[:value] = number_with_precision(value, opts) unless hidden_for_integer
end
elsif column.date? || column.time?
@options[:value] = value ? I18n.l(value, :format => @options.delete(:format)) : nil
end
end

render_without_localization
end

alias_method_chain :render, :localization
end
75 changes: 4 additions & 71 deletions lib/delocalize/rails_ext/active_record.rb
Original file line number Diff line number Diff line change
@@ -1,72 +1,5 @@
require 'active_record'

# let's hack into ActiveRecord a bit - everything at the lowest possible level, of course, so we minimalize side effects
ActiveRecord::ConnectionAdapters::Column.class_eval do
def date?
klass == Date
end

def time?
klass == Time
end
end

module ActiveRecord::AttributeMethods::Write
def type_cast_attribute_for_write(column, value)
return value unless column

value = Numeric.parse_localized(value) if column.number? && I18n.delocalization_enabled?
column.type_cast_for_write value
end
end

ActiveRecord::Base.class_eval do
def write_attribute_with_localization(attr_name, original_value)
new_value = original_value
if column = column_for_attribute(attr_name.to_s)
if column.date?
new_value = Date.parse_localized(original_value) rescue original_value
elsif column.time?
new_value = Time.parse_localized(original_value) rescue original_value
end
end
write_attribute_without_localization(attr_name, new_value)
end
alias_method_chain :write_attribute, :localization

define_method :_field_changed? do |attr, old, value|
if column = column_for_attribute(attr)
if column.number? && column.null && (old.nil? || old == 0) && value.blank?
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
# Hence we don't record it as a change if the value changes from nil to ''.
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
# be typecast back to 0 (''.to_i => 0)
value = nil
elsif column.number?
value = column.type_cast(Numeric.parse_localized(value))
else
value = column.type_cast(value)
end
end
old != value
end

def define_method_attribute=(attr_name)
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
method_body, line = <<-EOV, __LINE__ + 1
def #{attr_name}=(original_time)
time = original_time
unless time.acts_like?(:time)
time = time.is_a?(String) ? (I18n.delocalization_enabled? ? Time.zone.parse_localized(time) : Time.zone.parse(time)) : time.to_time rescue time
end
time = time.in_time_zone rescue nil if time
write_attribute(:#{attr_name}, original_time)
@attributes_cache["#{attr_name}"] = time
end
EOV
generated_attribute_methods.module_eval(method_body, __FILE__, line)
else
super
end
end
if Gem::Version.new(ActionPack::VERSION::STRING) >= Gem::Version.new('4.0.0.beta')
require 'delocalize/rails_ext/active_record_rails4'
else
require 'delocalize/rails_ext/active_record_rails3'
end
80 changes: 80 additions & 0 deletions lib/delocalize/rails_ext/active_record_rails3.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
require 'active_record'

require 'active_record/connection_adapters/abstract/schema_definitions'
begin
require 'active_record/connection_adapters/column'
rescue LoadError
# Not Rails 3.1, it seems
end

# let's hack into ActiveRecord a bit - everything at the lowest possible level, of course, so we minimalize side effects
ActiveRecord::ConnectionAdapters::Column.class_eval do
def date?
klass == Date
end

def time?
klass == Time
end
end

ActiveRecord::Base.class_eval do
def write_attribute_with_localization(attr_name, original_value)
new_value = original_value
if column = column_for_attribute(attr_name.to_s)
if column.date?
new_value = Date.parse_localized(original_value) rescue original_value
elsif column.time?
new_value = Time.parse_localized(original_value) rescue original_value
end
end
write_attribute_without_localization(attr_name, new_value)
end
alias_method_chain :write_attribute, :localization

def convert_number_column_value_with_localization(value)
value = convert_number_column_value_without_localization(value)
value = Numeric.parse_localized(value) if I18n.delocalization_enabled?
value
end
alias_method_chain :convert_number_column_value, :localization


define_method( (Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new('3.2.9')) ? :field_changed? : :_field_changed? ) do |attr, old, value|
if column = column_for_attribute(attr)
if column.number? && column.null && (old.nil? || old == 0) && value.blank?
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
# Hence we don't record it as a change if the value changes from nil to ''.
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
# be typecast back to 0 (''.to_i => 0)
value = nil
elsif column.number?
value = column.type_cast(convert_number_column_value_with_localization(value))
else
value = column.type_cast(value)
end
end
old != value
end
end

ActiveRecord::Base.instance_eval do
def define_method_attribute=(attr_name)
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
method_body, line = <<-EOV, __LINE__ + 1
def #{attr_name}=(original_time)
time = original_time
unless time.acts_like?(:time)
time = time.is_a?(String) ? (I18n.delocalization_enabled? ? Time.zone.parse_localized(time) : Time.zone.parse(time)) : time.to_time rescue time
end
time = time.in_time_zone rescue nil if time
write_attribute(:#{attr_name}, original_time)
@attributes_cache["#{attr_name}"] = time
end
EOV
generated_attribute_methods.module_eval(method_body, __FILE__, line)
else
super
end
end
end
72 changes: 72 additions & 0 deletions lib/delocalize/rails_ext/active_record_rails4.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require 'active_record'

# let's hack into ActiveRecord a bit - everything at the lowest possible level, of course, so we minimalize side effects
ActiveRecord::ConnectionAdapters::Column.class_eval do
def date?
klass == Date
end

def time?
klass == Time
end
end

module ActiveRecord::AttributeMethods::Write
def type_cast_attribute_for_write(column, value)
return value unless column

value = Numeric.parse_localized(value) if column.number? && I18n.delocalization_enabled?
column.type_cast_for_write value
end
end

ActiveRecord::Base.class_eval do
def write_attribute_with_localization(attr_name, original_value)
new_value = original_value
if column = column_for_attribute(attr_name.to_s)
if column.date?
new_value = Date.parse_localized(original_value) rescue original_value
elsif column.time?
new_value = Time.parse_localized(original_value) rescue original_value
end
end
write_attribute_without_localization(attr_name, new_value)
end
alias_method_chain :write_attribute, :localization

define_method :_field_changed? do |attr, old, value|
if column = column_for_attribute(attr)
if column.number? && column.null && (old.nil? || old == 0) && value.blank?
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
# Hence we don't record it as a change if the value changes from nil to ''.
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
# be typecast back to 0 (''.to_i => 0)
value = nil
elsif column.number?
value = column.type_cast(Numeric.parse_localized(value))
else
value = column.type_cast(value)
end
end
old != value
end

def define_method_attribute=(attr_name)
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
method_body, line = <<-EOV, __LINE__ + 1
def #{attr_name}=(original_time)
time = original_time
unless time.acts_like?(:time)
time = time.is_a?(String) ? (I18n.delocalization_enabled? ? Time.zone.parse_localized(time) : Time.zone.parse(time)) : time.to_time rescue time
end
time = time.in_time_zone rescue nil if time
write_attribute(:#{attr_name}, original_time)
@attributes_cache["#{attr_name}"] = time
end
EOV
generated_attribute_methods.module_eval(method_body, __FILE__, line)
else
super
end
end
end
Loading

0 comments on commit d89041f

Please sign in to comment.