rails 3.0 attr_encrypted existing database - ruby-on-rails-3

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

Related

Delete a Post without the associated Task

I would like to be able to destroy my Post without the Task which is associated. But I'm currently facing a SQL error which say:
ActiveRecord::InvalidForeignKey (SQLite3::ConstraintException: FOREIGN KEY constraint failed: DELETE FROM "posts" WHERE "posts"."id" = ?):
After a few search, I saw that is coming from associations and Foreign Key. But I cannot solve the problem for the moment.
I have tested to put (optional: true) into my model.
I also have tried to change the foreign key into (, on_delete: :cascade) & (, on_delete: :nullify) but it's still not working.
My code =
//Post Model
class Post < ApplicationRecord
has_one :task
end
//Task Model
class Task < ApplicationRecord
belongs_to :post, optional: true
end
To destroy :
//Destroy_one into the Post Controller
def destoy_one
#post.destroy
end
Migration File : (also tried with on_delete: :nullify)
class EditForeightKeys < ActiveRecord::Migration[5.1]
def change
# remove the old foreign_key
remove_foreign_key :tasks, :post
# add the new foreign_key
add_foreign_key :tasks, :post, on_delete: :cascade
end
end
Do you have any other solution for that ?
I solve this issue by implement a deleted_state into Posts
def destroy_one
#post.update(deleted_state: true)
end
And after you can put a default scope into your Post model like this :
default_scope { where(deleted_state: false) }
Like this all will work without problems !!
You can use callbacks on destroy action for User as a variant:
class Post
before_destroy :update_tasks
private
def update_tasks
self.tasks.update_all(post_id: nil)
end
end

MassAssignmentSecurity Error when using attr_encrypted (attr_encryptor) gem

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

Thinking Sphinx with has_many through indexes just fine but saving item crashes

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

Rails 3: Apply the same validation rules to multiple table fields

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

Rails - How to add a BLOB column using ActiveRecord?

I need to create a BLOB column to store some text content.
I have read somewhere that I need to do the following:
class AddVersionCommentToMetaData < ActiveRecord::Migration
def self.up
add_column :meta_data, :version_comment, :binary, :limit => 10.megabyte
end
def self.down
remove_column :meta_data, :version_comment
end
end
However, it gives the following error message:
PGError: ERROR: type modifier is not allowed for type "bytea" LINE 1:
..."meta_data" ADD COLUMN "version_comment_extended" bytea(1048...
^ : ALTER
TABLE "meta_data" ADD COLUMN "version_comment_extended"
bytea(10485760)
Any idea?
Please note that I am using PostgreSQL.
Thanks!
The migration seems to be correct except the down part. It should be:
class AddVersionCommentToMetaData < ActiveRecord::Migration
def self.up
add_column :meta_data, :version_comment, :binary, :limit => 10.megabyte
end
def self.down
remove_column :meta_data, :version_comment
end
end
double check for typos. And what version of rails are you using? It works well in rails 3.0.7.