Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add multiparameter assignment #95

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/active_attr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module ActiveAttr
autoload :Logger
autoload :MassAssignment
autoload :MassAssignmentSecurity
autoload :MultiAttr
autoload :Model
autoload :QueryAttributes
autoload :TypecastedAttributes
Expand Down
47 changes: 43 additions & 4 deletions lib/active_attr/mass_assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,49 @@ module MassAssignment
#
# @since 0.1.0
def assign_attributes(new_attributes, options={})
new_attributes.each do |name, value|
writer = "#{name}="
send writer, value if respond_to? writer
end if new_attributes
if new_attributes
prepare_attributes(new_attributes).each do |name, value|
writer = "#{name}="
send writer, value if respond_to? writer
end
end
end

def prepare_attributes(attrs)
multi_attr_pairs = []

attrs.inject({}) do |res, (name, value)|
if name.to_s.include?("(")
multi_attr_pairs << [name, value]
else
res[name] = value
end

res
end.merge(prepare_multi_attrs(multi_attr_pairs))
end

def prepare_multi_attrs(pairs)
attributes = {}

pairs.each do |pair|
multiparameter_name, value = pair
attribute_name = multiparameter_name.split("(").first
attributes[attribute_name] = {} unless attributes.include?(attribute_name)

parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
end

attributes.inject({}) { |res, (k, v)| res[k] = ActiveAttr::MultiAttr.new(v); res }
end

def type_cast_attribute_value(multiparameter_name, value)
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
end

def find_parameter_position(multiparameter_name)
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
end

# Mass update a model's attributes
Expand Down
48 changes: 48 additions & 0 deletions lib/active_attr/multi_attr.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module ActiveAttr
class MultiAttr
class MissingParameter < Exception; end

attr_accessor :hash
delegate :[], :has_key?, :to => :hash

def initialize(hash)
self.hash = hash
end

def to_time
# If Date bits were not provided, error
raise MissingParameter if [1,2,3].any?{|position| !has_key?(position)}
# If Date bits were provided but blank, then return nil
return nil if (1..3).any? {|position| hash[position].blank?}

set_values = (1..max_position).collect{|position| hash[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]}
instantiate_time_object(set_values)
end

def to_date
return nil if (1..3).any? {|position| hash[position].blank?}
set_values = [hash[1], hash[2], hash[3]]
begin
Date.new(*set_values)
rescue ArgumentError # if Date.new raises an exception on an invalid date
instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
end
end

def max_position(upper_cap = 100)
[hash.keys.max, upper_cap].min
end

private

def instantiate_time_object(values)
if Time.respond_to?(:zone) && Time.zone
Time.zone.local(*values)
else
Time.local(*values)
end
end
end
end
8 changes: 7 additions & 1 deletion lib/active_attr/typecasted_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,13 @@ def attribute_before_type_cast(name)
#
# @since 0.5.0
def attribute(name)
typecast_attribute(_attribute_typecaster(name), super)
value = super

if value.is_a?(ActiveAttr::MultiAttr)
typecast_multiattr(_attribute_type(name), value)
else
typecast_attribute(_attribute_typecaster(name), value)
end
end

# Calculates an attribute type
Expand Down
15 changes: 15 additions & 0 deletions lib/active_attr/typecasting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require "active_attr/typecasting/object_typecaster"
require "active_attr/typecasting/string_typecaster"
require "active_attr/typecasting/unknown_typecaster_error"
require "active_attr/multi_attr"

module ActiveAttr
# Typecasting provides methods to typecast a value to a different type
Expand Down Expand Up @@ -60,5 +61,19 @@ def typecaster_for(type)

typecaster.new if typecaster
end

def typecast_multiattr(klass, value)
if klass == Time
value.to_time
elsif klass == Date
value.to_date
else
values = (1..value.max_position).collect do |position|
raise ActiveAttr::MultiAttr::MissingParameter if !value.has_key?(position)
value[position]
end
klass.new(*values)
end
end
end
end
7 changes: 7 additions & 0 deletions spec/unit/active_attr/mass_assignment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ module ActiveAttr
person = mass_assign_attributes(:middle_name => "J")
person.middle_name.should be_nil
end

it "creates multi attr" do
person = mass_assign_attributes('first_name(1i)' => '100', 'first_name(2s)' => 'test')
person.first_name.should be_a(ActiveAttr::MultiAttr)
person.first_name.hash[1].should == 100
person.first_name.hash[2].should == 'test'
end
end

describe "#assign_attributes", :assign_attributes, :lenient_mass_assignment_method
Expand Down
45 changes: 45 additions & 0 deletions spec/unit/active_attr/typecasted_attributes_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,51 @@ def self.name
subject.should_receive(:attribute_before_type_cast).with("last_name")
subject.last_name_before_type_cast
end

context "MultiValue" do
before do
model_class.class_eval do
attribute :time, :type => Time
attribute :date, :type => Date
end
end

it "typecasts MultiValue to time" do
subject.time = ActiveAttr::MultiAttr.new(1 => "2010", 2 => "10", 3 => "5")
subject.time.should == Time.local(2010, 10, 5)
end

it "typecasts MultiValue to date" do
subject.date = ActiveAttr::MultiAttr.new(1 => "2010", 2 => "10", 3 => "5")
subject.date.should == Date.new(2010, 10, 5)
end

context "custom class" do
it "typecasts MultiValue to custom class" do
custom_class = Class.new do
attr_accessor :a, :b
def initialize(a, b)
self.a = a
self.b = b
end
end
model_class.class_eval do
attribute :custom, :type => custom_class
end

subject.custom = MultiAttr.new(1 => "foo", 2 => "bar")
result = subject.custom
result.should be_a(custom_class)
result.a.should == "foo"
result.b.should == "bar"
end
end

it "raises ActiveAttr::MultiAttr::MissingParameter on arguments error" do
subject.time = MultiAttr.new(1 => "2010", 3 => "5")
expect { subject.time }.to raise_error(ActiveAttr::MultiAttr::MissingParameter)
end
end
end

describe ".inspect" do
Expand Down