For my rails 3.2.3 app, I am using attr_encryptor, which is a fork by danpal of attr_encrypted. I have followed the instructions as given here, but I am getting the following error message when I try to create a new Patient record:
ActiveModel::MassAssignmentSecurity::Error in PatientsController#create
Can't mass-assign protected attributes: mrn, last_name, first_name, date_of_birth(1i), date_of_birth(2i), date_of_birth(3i)
As the instructions say, I have added encrypted_#{field}, encrypted_#{field}_salt, and encrypted_#{field}_iv columns to my Patients table while dropping their non-encrypted counterparts.
The Patient model looks like:
class Patient < ActiveRecord::Base
attr_accessible :age, :gender
attr_encrypted :last_name, :key => 'key 1'
attr_encrypted :first_name, :key => 'key 2'
attr_encrypted :mrn, :key => 'key 3'
attr_encrypted :date_of_birth, :key => 'key 4'
# ...
end
My create method in my Patient controller looks like:
PatientsController < ApplicationController
# ...
def create
#patient = Patient.new
#patient.first_name = params[:patient][:first_name]
#patient.last_name = params[:patient][:last_name]
#patient.mrn = params[:patient][:mrn]
#patient.date_of_birth = Date.new(params[:patient]['date_of_birth(1i)'],
params[:patient]['date_of_birth(2i)'],
params[:patient]['date_of_birth(3i)'])
if #patient.save
# do stuff
else
# do other stuff
end
end
# ...
end
What am I doing wrong? Thanks in advance for the help!
You need to mark these attributes with attr_accessible as well as attr_encrypted since the latter does not imply the former.
This might also be relevant for the date field: Correct way to handle multiparameter attributes corresponding to virtual attributes
Related
I am doing some metaprogramming where I add accessible attributes to a model and would like to know how to check and see whether these attributes are accessible.
Looked at the documentation but could find no reference.
http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html#method-i-attr_accessible
I know I can do something like object.instance_methods but that dozen't really filter it down to accessible.
Is there some method that will return the accessible attributes?
Use accessible_attributes and protected_attributes.
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name
end
User.accessible_attributes
# => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"", "first_name", "last_name"}>
User.protected_attributes
# => #<ActiveModel::MassAssignmentSecurity::BlackList: {"id", "type"}>
If you call attr_protected and not attr_accessible in your class, then ALL of your attributes except for those in your Blacklist will be accessible.
Assuming you have a User model with these attributes: :id, :lastname, :firstname, :email
and your model class is:
class User < ActiveRecord::Base
attr_accessible :lastname, :firstname
end
You can have the accessible list like this:
User.attr_accessible[:default].to_a
=> [:lastname, :firstname]
Moreover you can have the list of no-accessible attributes:
User.new.attributes.keys - User.attr_accessible[:default].to_
=> [:id, :email]
I hope this help
Thinking sphinx indexes just fine and lookup works well on my attributes. However, my attempt to save a model instance crashes.
class Location < ActiveRecord::Base
has_many :operation_intervals_locations
has_many :operation_intervals, :through => :operation_intervals_locations
define_index "location" do
# indexes here...
# tried this syntax
has operation_intervals(:start_int), :type => :integer
has operation_intervals(:end_int), :type => :integer
has operation_intervals(:days_int), :type => :integer
# and this one
has operation_intervals.start_int, :type => :integer
has operation_intervals.end_int, :type => :integer
has operation_intervals.days_int, :type => :integer
end
end
class OperationInterval < ActiveRecord::Base
attr_accessible :start_int, :end_int, :days_int
end
Whenever I do the following:
Location.search("foo") # get the search initialized
l = Location.first
l.name = "bar"
l.save(:validate => false)
I get the following:
# joining is working just fine
OperationInterval Load (0.3ms) SELECT `operation_intervals`.* FROM `operation_intervals` INNER JOIN `operation_intervals_locations` ON `operation_intervals`.`id` = `operation_intervals_locations`.`operation_interval_id` WHERE `operation_intervals_locations`.`location_id` = 1
# here's where I'm getting my crash
NoMethodError: undefined method `end_int' for #<ActiveRecord::Relation:0xe048450>
from /usr/local/rvm/gems/ruby-1.9.3-p194#rails326/gems/activerecord-3.2.6/lib/active_record/relation/delegation.rb:45:in `method_missing'
Edit:
I am using delayed delta -- delayed job. I think the issue is happening as thinking_sphinx is trying to push the job to the queue.
Okay.. Thinking sphinx works well with many to many relationships
The line
has operation_intervals.days_int, :type => :integer
should NOT have :type => :integer because a location can have multiple operation_intervals and the real type is array!
using this code instead will solve the issue:
has operation_intervals.days_int
The Problem:
I am getting an error message when submitting my form that says:
ActiveModel::MassAssignmentSecurity::Error in AdmissionRecordsController#create
Can't mass-assign protected attributes: admission_record
My Setup:
I am using Rails 3.2.3, with extra gems including Cocoon 1.0.14 and Simple_Form 2.0.2
The View:
My app/views/admission_records/_form.html.haml looks like:
= simple_form_for [#admission, #record] do |f|
= f.simple_fields_for :vital_signs, #record.vital_signs.build do |vs|
= render :partial => "vital_sign_fields", :locals => { :f => vs }
= link_to_add_association "Add Vital Signs", f, :vital_signs
= f.submit
And my app/views/admission_records/_vital_sign_fields.html.haml looks like:
.nested-fields
= f.label :sbp
= f.text_field :sbp
...
= link_to_remove_association "Remove Vital Sign"
What I am basically trying to do is that I have a resource called AdmissionRecord nested within another resource called PatientAdmission (route.rb shown below). I have another resource called VitalSign which I want to be able to create via a nested form (using cocoon and simple_form) when creating the AdmissionRecord
My config/routes.rb file looks like:
resources :patient_admissions do
resources :admission_records
end
The Models:
My app/models/patient_admission.rb looks like:
class PatientAdmission < ActiveRecord::Base
has_many :admission_records, :dependent => :destroy
end
My app/models/admission_record.rb looks like:
class AdmissionRecord < ActiveRecord::Base
belongs_to :patient_admission
has_many :vital_signs, :dependent => :destroy
accepts_nested_attributes_for :vital_signs, :rejects_if => :all_blank, :allow_destroy => true
attr_accessible :vital_signs_attributes
end
And my app/models/vital_sign.rb looks like:
class VitalSign < ActiveRecord::Base
belongs_to :admission_record
attr_accessible # just fields that appear in the form
end
The Controller:
The new and create methods in my AdmissionRecordsController looks like:
before_filter do
#admission = PatientAdmission.find(params[:patient_admission_id])
end
def new
#record = #admission.admission_records.build
end
def create
#record = #admission.admission_records.build(params[:admission_record])
#vital_sign = #record.vital_signs.build(params[:vital_signs])
#vital_sign.save
if #record.save
# Flash success and redirect to the right place
else
# Flash error and render :new
end
end
The Plea:
Please help me find where I'm going wrong. I've googled for hours and have looked at other examples and source code for demo apps such as those found in cocoon_simple_form_demo, but still can't seem to fix this error. If there's any other piece of information needed to debug this problem, please let me know. Thanks!
Okay I just had this problem and fixed it by entering one line of code in the belongs_to model.
# patient_admission.rb
Class PatientAdmission < ActiveRecord::Base
attr_accessible :admission_record_attributes
accepts_nested_attributes_for :admission_record
...
end
Here is another solution to it :)
I have created a model with several fields that should accept the same data format (strings, but can be anything, FWIW). I'd like to apply the same validation rule to all those fields. Of course, I can just go ahead and copy/paste stuff, but that would be against DRY principle, and common sense too...
I guess this one is pretty easy, but I'm a Rails newcomer/hipster, so excuse-moi for a trivial question. =)
So if you had say three fields to validate:
:first_name
:last_name
:age
And you wanted them all to be validated? So something like this:
validates_presence_of :first_name, :last_name, :age
Edit: There are numerous different validation methods in Rails )and they're wonderfully flexible). For the format of the field you can use validates_format_of, and then use a Regular Expression to match against it. Here's an example of matching an email:
validates_format_of :email, :with => /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
I'd check out the Active Record Validations and Callbacks guide; it provides comprehensive insight about a lot of the features Active Record provides in terms of validation. You can also check out the documentation here.
If you are using any of the built-in validations (presence, length_of) you can apply a single validation to multiple attributes like this:
validates_presence_of :name, :email
If you have custom logic you can create a validator object to house the code and apply it individually
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << (options[:message] || "is not an email") unless
value =~ /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
end
end
def Person
validates :home_email, :email => true
validates :work_email, :email => true
end
see: http://thelucid.com/2010/01/08/sexy-validation-in-edge-rails-rails-3/
In Rails 4 you can apply the same validation to multiple columns by using a loop:
[:beds, :baths].each do |column|
validates column, allow_blank: true, length: { maximum: 25 }
end
Both beds and baths are validated using the same validations.
Edit:
In Rails 4.2 you can do this same thing by putting multiple symbols after the validates function call. Example:
validates :beds, :baths, allow_blank: true
Use Themis for this:
# Describe common validation in module
module CommonValidation
extend Themis::Validation
validates_presence_of :foo
validates_length_of :bar, :maximum => 255
end
class ModelA < ActiveRecord::Base
# import validations
include CommonValidation
end
class ModelB < ActiveRecord::Base
# import validations
include CommonValidation
end
Or you can use "with_options", for example:
with_options presence: true do |video|
REQUIRED_COLUMNS.map do |attr|
video.validates attr
end
end
In a rails 3.0 I need to encrypt an existing text field.
There a table memos that contains a text field "note". I've create an encrypted_note field
and added in the model:
attr_encrypted :note, :key => 'a secret key'
For now when I load an existing record the "note" is empty. I'm assuming that attr_encrypted try to decrypt...but the field has note been encrypted yet!
attr_encrypted works well for new records but wondering what would be the best strategy to encrypt the existing records?
Does instance_variable_get('#note') or read_attribute('note') work?
If so, you can probably do something like this in the Rails console:
User.all.each do |user|
user.note = user.instance_variable_get('#note')
user.save
end
Here is the trick to clear the unencrypted column as the encrypted one get populated!
in the model add:
before_update :clear_note
def clear_note
if encrypted_note != nil && read_attribute('note') != nil
write_attribute('note','')
end
end
Assuming you start with your model Thing with the un-encrypted attribute note.
1) Add a migration to add a field encrypted_note and populate it
class EncryptThing < ActiveRecord::Migration
def up
rename_column :things, :note, :old_note
add_column :things, :encrypted_note, :string
# if you want to use per-attribute iv and salt:
# add_column :things, :encrypted_note_iv, :string
# add_column :things, :encrypted_note_salt, :string
Thing.find_each do |t|
t.note = t.old_note
t.save
end
remove_column :things, :old_note
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
2) Add a line to your model to specify the encrypted attribute:
attr_encrypted :note, :key => Rails.application.config.key
# if you want to use per-attribute iv and salt, add this to the line above:
# , :mode => :per_attribute_iv_and_salt
3) run your migration
rake db:migrate