O ActiveRecord às vezes parece uma caixa preta de magia e unicórnios, podemos odiá-lo, mas não podemos negar que ele faz muitas coisas por nós. Uma dessas coisas é instanciar e atribuir magicamente objetos de data aos atributos de data do nosso modelo, mas o que acontece quando nos desviamos de seu caminho mágico?
Se você está construindo uma aplicação Rails sem AR ao enviar formulários com datas, você pode ter tropeçado neste tipo de parâmetro
{ "name" => "James Bond", "birth_date(3i)"=>"11", "birth_date(2i)"=>"11", "birth_date(1i)"=>"1920" }
Digamos que você tenha um modelo simples com um atributo birth_date e name, por uma questão de simplicidade, vamos assumir que BaseModel adiciona todos os clichês necessários para fazer nossa classe se comportar como um modelo ActiveRecord
class Person < BaseModel
attr_accessor :birth_date, :name
def initialize(attributes = nil)
super
end
end
Agora, ao enviar, nosso nome de formulário será atribuído, mas data de nascimento não.
A razão é simples: data de nascimento (3i), data de nascimento (2i) nem data de nascimento (1i) são atributos da classe, mas o “ `assign attribute“` do AR faz toda a mágica necessária para atribuir datas e outros atributos multiparâmetros .
Conforme afirmado anteriormente, podemos basear nossa implementação em ActiveRecord assign_attribute
e iterar através dos atributos passados, selecionar aqueles que são do tipo multiparâmetro, extrair seus valores e atribuí-los a um novo hash a ser enviado à classe pai.
class Person < BaseModel
attr_accessor :birth_date, :name
def initialize(attributes = nil)
super
assign_dates(attributes) if attributes
end
protected
def assign_dates(attributes)
new_attributes = attributes.stringify_keys
multiparameter_attributes = extract_multiparameter_attributes(new_attributes)
multiparameter_attributes.each do |multiparameter_attribute, values_hash|
set_values = (1..3).collect{ |position| values_hash[position].to_i }
self.send("#{multiparameter_attribute}=", Date.new(*set_values))
end
end
def extract_multiparameter_attributes(new_attributes)
multiparameter_attributes = []
new_attributes.each do |k, v|
if k.include?('(')
multiparameter_attributes << [k, v]
end
end
extract_attributes(multiparameter_attributes)
end
def extract_attributes(pairs)
attributes = {}
pairs.each do |pair|
multiparameter_name, value = pair
attribute_name = multiparameter_name.split('(').first
attributes[attribute_name] = {} unless attributes.include?(attribute_name)
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= value
end
attributes
end
def find_parameter_position(multiparameter_name)
multiparameter_name.scan(/(([0-9]*).*)/).first.first.to_i
end
end
Mergulhar na fonte do ActiveRecord é um exercício gratificante que ajuda muito a compreender as capacidades aparentemente mágicas desta caixa preta.