Question

I'm new to rails and I am using Rails 4 and the attr_encrypted gem to encrypt some fields (SSN, names, date of birth, etc ) which will all be inserted into the database in varchar columns. In the form view, I'm using date_select to generate the date of birth field (dob), but I am having trouble trying to convert the selected date into a string so that attr_encrypted can encrypt it for insertion into the database.

_form.html.erb

<%= f.label :dob %><br>
<%= f.date_select :dob,  { start_year: 1900, :order => [ :month, :day, :year ] , prompt: true, add_month_numbers: true, use_two_digit_numbers: true } %>

The error given is a mass assignment error, but I don't know how/where (the controller/model) to convert the hash into a string so that the attr_encrypted gem will be able to encrypt it. What's the best way to accomplish this?

Was it helpful?

Solution

I've found that attr_encrypted breaks Rails' automatic composition of dates from date_select. The simplest solution I found was to assemble the date string myself and rewrite the params hash. In your controller:

protected    

def compose_date(attributes, property)
  # if the date is already composed, don't try to compose it
  return unless attributes[property].nil?

  keys, values = [], []

  # find the keys representing the components of the date
  attributes.each_key {|k| keys << k if k.start_with?(property) }

  # assemble the date components in the right order and write to the params
  keys.sort.each { |k| values << attributes[k]; attributes.delete(k); }
  attributes[property] = values.join("-") unless values.empty?
end

Then you can proceed as normal and everything will be fine:

def create
  compose_date(params[:client], "dob")

  @client = Client.new(params[:client])
  ...
end

EDIT: I forgot this at first but I had to do some extra work to get the date to store properly in the database. The attr_encrypted gem always wants to store strings, so if your data is not a string then you'll want to show it how to marshal it.

I created a module to handle data encryption:

module ClientDataEncryption
  def self.included(base)
    base.class_eval do
      attr_encrypted :ssn, :key => "my_ssn_key"
      attr_encrypted :first_name, :last_name, :key => "my_name_key"
      attr_encrypted :dob, :key => "my_dob_key",
                     :marshal => true, :marshaler => DateMarshaler
    end
  end

  class DateMarshaler
    def self.dump(date)
      # if our "date" is already a string, don't try to convert it
      date.is_a?(String) ? date : date.to_s(:db)
    end

    def self.load(date_string)
      Date.parse(date_string)
    end
  end
end

Then included it in my Client model.

OTHER TIPS

I am writing a loan application form and was running into the same issue with attr_encrypted on my Owner model's date_of_birth attribute which led me here. I found Wally Altman's solution to be almost perfect with a few changes necessary to use in my application:

  • Using this in a nested form
  • Strong parameters
  • Multiple model instances

I copied the DateMarshaler and the compose_date() method in verbatim, then in my controller I added a loop that goes through all the Owner objects we're editing here.

def resource_params
  params[:loan_application][:owners_attributes].each do |owner| 
    compose_date(owner[1], 'date_of_birth')
    # If there were more fields that needed this I'd put them here
  end
  params.require(:loan_application).permit(:owners_attributes =>
    [ # Regular strong params stuff here ])
end

It worked like a charm on any number of nested models!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top