Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Fixes #6825, adds tests covering cases and error possibilities #6873

Merged
merged 1 commit into from

3 participants

@mauricio

The current implementation on Rails is not capable of handling an object with time only, it only allows you to create an object with both time and date, which isn't really nice as you can create a time only column in almost all database systems.

This fix adds support for time only object creation (if the column is created as :time) by using Ruby's Time object. The date fields will default to "January 1, 1970" since Ruby doesn't have an object for time only. Implementation also includes specs showing what's the correct behavior and also a failure if you try to create a time only object of a column of type datetime.

This pull request fixes issue #6825.

@carlosantoniodasilva

It looks ok to me, lets just wait some more feedback before merging, thanks!

activerecord/lib/active_record/attribute_assignment.rb
((5 lines not shown))
- raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
+ # If column is a :time (and not :date or :timestamp) there is no need to validate if
+ # there are year/month/day fields
+ if column_for_attribute(name).type == :time
+ # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
+ {1 => 1970, 2 => 1, 3 => 1}.each do |key,value|
+ values_hash_from_param[key] ||= value
+ end
+ else
+ # else column is a timestamp, so if Date bits were not provided, error
+ if missing_parameter = [1,2,3].detect{ |position| !values_hash_from_param.has_key?(position) }
+ raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter}i)")
+ end
+ return nil if (1..3).any? { |position| values_hash_from_param[position].blank? }
+ end
+
max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
# If Date bits were provided but blank, then return nil
@rafaelfranca Owner

I think this comment should be moved with 196

Not following. What comment?

@rafaelfranca Owner
# If Date bits were provided but blank, then return nil

Should be

if missing_parameter = [1,2,3].detect{ |position| !values_hash_from_param.has_key?(position) }
  raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter}i)")
end

# If Date bits were provided but blank, then return nil
return nil if (1..3).any? { |position| values_hash_from_param[position].blank? }

Ah missed that: # If Date bits were provided but blank, then return nil

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@mauricio

I have force-pushed the same commit again and it's showing two here, not nice.

activerecord/lib/active_record/attribute_assignment.rb
((6 lines not shown))
- max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
- # If Date bits were provided but blank, then return nil
- return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
+ # If column is a :time (and not :date or :timestamp) there is no need to validate if
+ # there are year/month/day fields
+ if column_for_attribute(name).type == :time
+ # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
+ {1 => 1970, 2 => 1, 3 => 1}.each do |key,value|
+ values_hash_from_param[key] ||= value
+ end
+ else
+ # else column is a timestamp, so if Date bits were not provided, error
+ if missing_parameter = [1,2,3].detect{ |position| !values_hash_from_param.has_key?(position) }
+ raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter}i)")
+ end
+ # If Date bits were provided but blank, then return nil
@rafaelfranca Owner

Put a blank line before this comment. I think it make easier to read.

Cool, anything else?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rafaelfranca

Yes, it is showing two commits.

I saw that you refactored activerecord/lib/active_record/errors.rb. I think is better to make it in a separate commit, in this pull request.

@mauricio

Separate commits? Shouldn't it all be squashed in a single commit?

The change in activerecord/lib/active_record/errors.rb is necessary to get the exception message to be correctly initialized, it wasn't loading the correct error before.

@rafaelfranca

We only ask to squash if the commit does not make sense alone.

I thought that changes at activerecord/lib/active_record/errors.rb was only refactoring, but since it is required to initialize the message so it is fine to be done in the same commit.

So, put the blank line before the comment and squash all the commits that I'll merge.

@mauricio

Cool, added the line and it's a single commit now.

@rafaelfranca

@mauricio now we have 3 commits. Very strange.

@mauricio mauricio Fixes #6825, adds tests covering cases and error possibilities, also …
…changes SQLite3 driver to correctly generate a time column instead of datetime
ab7c1c4
@mauricio

Whoops, fixed.

@rafaelfranca rafaelfranca merged commit 2076efe into rails:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 27, 2012
  1. @mauricio

    Fixes #6825, adds tests covering cases and error possibilities, also …

    mauricio authored
    …changes SQLite3 driver to correctly generate a time column instead of datetime
This page is out of date. Refresh to see the latest.
View
31 activerecord/lib/active_record/attribute_assignment.rb
@@ -158,11 +158,12 @@ def execute_callstack_for_multiparameter_attributes(callstack)
begin
send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
rescue => ex
- errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
end
end
unless errors.empty?
- raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
+ error_descriptions = errors.map { |ex| ex.message }.join(",")
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
end
end
@@ -180,15 +181,27 @@ def read_value_from_parameter(name, values_hash_from_param)
end
def read_time_parameter_value(name, values_hash_from_param)
- # If Date bits were not provided, error
- raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
- max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
- # If Date bits were provided but blank, then return nil
- return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
+ # If column is a :time (and not :date or :timestamp) there is no need to validate if
+ # there are year/month/day fields
+ if column_for_attribute(name).type == :time
+ # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
+ {1 => 1970, 2 => 1, 3 => 1}.each do |key,value|
+ values_hash_from_param[key] ||= value
+ end
+ else
+ # else column is a timestamp, so if Date bits were not provided, error
+ if missing_parameter = [1,2,3].detect{ |position| !values_hash_from_param.has_key?(position) }
+ raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter}i)")
+ end
+
+ # If Date bits were provided but blank, then return nil
+ return nil if (1..3).any? { |position| values_hash_from_param[position].blank? }
+ end
- set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
+ set_values = (1..max_position).collect{ |position| values_hash_from_param[position] }
# If Time bits are not there, then default to 0
- (3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]}
+ (3..5).each { |i| set_values[i] = set_values[i].blank? ? 0 : set_values[i] }
instantiate_time_object(name, set_values)
end
View
10 activerecord/lib/active_record/errors.rb
@@ -106,13 +106,11 @@ class StaleObjectError < ActiveRecordError
attr_reader :record, :attempted_action
def initialize(record, attempted_action)
+ super("Attempted to #{attempted_action} a stale object: #{record.class.name}")
@record = record
@attempted_action = attempted_action
end
- def message
- "Attempted to #{attempted_action} a stale object: #{record.class.name}"
- end
end
# Raised when association is being configured improperly or
@@ -168,9 +166,9 @@ class UnknownAttributeError < NoMethodError
class AttributeAssignmentError < ActiveRecordError
attr_reader :exception, :attribute
def initialize(message, exception, attribute)
+ super(message)
@exception = exception
@attribute = attribute
- @message = message
end
end
@@ -189,12 +187,10 @@ class UnknownPrimaryKey < ActiveRecordError
attr_reader :model
def initialize(model)
+ super("Unknown primary key for table #{model.table_name} in model #{model}.")
@model = model
end
- def message
- "Unknown primary key for table #{model.table_name} in model #{model}."
- end
end
class ImmutableRelation < ActiveRecordError
View
33 activerecord/test/cases/attribute_methods_test.rb
@@ -791,6 +791,39 @@ def title=(val); self.author_name = val; end
assert_equal "lol", topic.author_name
end
+ def test_setting_time_attribute
+ topic = Topic.new( "bonus_time(4i)"=> "01", "bonus_time(5i)" => "05" )
+ assert_equal 1, topic.bonus_time.hour
+ assert_equal 5, topic.bonus_time.min
+ end
+
+ def test_setting_date_attribute
+ topic = Topic.new( "written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11" )
+ assert_equal 1952, topic.written_on.year
+ assert_equal 3, topic.written_on.month
+ assert_equal 11, topic.written_on.day
+ end
+
+ def test_setting_date_and_time_attribute
+ topic = Topic.new(
+ "written_on(1i)" => "1952",
+ "written_on(2i)" => "3",
+ "written_on(3i)" => "11",
+ "written_on(4i)" => "13",
+ "written_on(5i)" => "55")
+ assert_equal 1952, topic.written_on.year
+ assert_equal 3, topic.written_on.month
+ assert_equal 11, topic.written_on.day
+ assert_equal 13, topic.written_on.hour
+ assert_equal 55, topic.written_on.min
+ end
+
+ def test_setting_time_but_not_date_on_date_field
+ assert_raise( ActiveRecord::MultiparameterAssignmentErrors ) do
+ Topic.new( "written_on(4i)" => "13", "written_on(5i)" => "55" )
+ end
+ end
+
private
def cached_columns
Topic.columns.find_all { |column|
Something went wrong with that request. Please try again.