Ruby ActiveRecord validation based on existing value in database - ruby-on-rails

I know the common ways of validation in Ruby.
My current problem is, I would like the insert to fail if the record exists with boolean set to true. Example
Columns in table are => Name, Address, Has_changed, id
Now if has_changed is set to true in table, I would like to add new entry in the table (in a separate call) which will have name, Address (new one), has_changed(set to false) and id. I would not like to update existing entry as I want to keep history for the record.
Is there a way to have such a validation by using Ruby?
Thanks in advance.

I think its better use "new" and "create" actions in your controller
class RecordsController < BaseController
def new
#record = Record.find(params[:id])
end
def create
Record.create(params[:record])
end
end
Your form just contains adress
In your model make a before_create hook
class Record < ActiveRecord::Base
default_value_for :has_changed, false
before_create :check_changed
...
private
def check_changed
changed_record = Record.where(name: name, has_changed: false).first
if changed_record && (changed_record.address != address)
changed_record.has_changed = true
changed_record.save
end
end
end
And add a validation for address to avoid duplication

Related

how to permit and validate parameter in ruby?

i'm working on validating parameter passed in ruby controller/model as below.
in the controller, i want to allow only to allow id and name parameter. and i want to pass that id and name parameter to the model class and validate they have acceptable values. i have following , is this a acceptable way to do it in ruby?
controller class
class PersonController
def create
Person.find_By_person_id(check_params[:id], check_params[:name])
end
...
private
def check_params
params.permit(:id, :name)
end
end
model class
class People
class<<self
def find_By_person_id(id, name)
//validate id and name have values
raise ArugmentError unless validate_params
//calls another service to get the person
end
private
def validate_params
return false if id is not integer || id is less than zero || name is empty string
end
end
end
You should consider using Active Record Validations (https://guides.rubyonrails.org/active_record_validations.html) and use check for presence of the :id and :name. You model will look like so:
people.rb
class People < ApplicationRecord
validates :name, presence: true
end
As for the id, Active Record generates a unique running id for any new record to be created, so I don't see any reason for you to need to validate it - unless you are passing another :id from the front end, in which case you should rename it so it won't conflict withe the :id for the record.

How to stop create in rails 5 without rolling back changes?

I need to check if a similar record exist in database before save, if true then update the existing record without saving, else create a new one
In rails 5:
returning false in a hook method doesn't halt callbacks and "throw :abort" is used instead.
the problem is using "throw :abort" rolls back any changes made in the before_save callback.
what I am trying to do is to check for a similar recored in "before_save" and if a similar record exist I need to update the current record and stop saving the new one.
I used
before_save :check
def check
if (similar record exist..)
update current...
return false <==========
end
true
end
but this is not working any more in Rails 5 so returning false doesn't stop it from saving the new record too.
and I tried
before_save :check
def check
if (exist..)
update current...
throw :abort <========
end
true
end
this stops saving current record to db but it perform "rollback" so the updated recored is missed !!
how can I do that ?
I think this is one possible way. This example if with a Product model looking for same name.
before_create :rollback_if_similar_exists
after_rollback :update_existing_record
def rollback_if_similar_exists
throw :abort if Product.exists? name: self.name
end
def update_existing_record
# do here what you need
puts name
puts "find the existing record"
puts "update data"
end
Here is a slightly different approach you could take:
Instead of using before_save, create your own validation and use assign_attributes instead of update or create since assign_attributes won't actually write to the database. You could then call the valid? function on your record to execute your validations. If you get a duplicate record error from the validation you defined, then have your code handle updating the existing record in the logic of your error handling.
Something like this in your controller:
#record.assign_attributes(my_parameter: params[:my_parameter])
if #record.valid?
#record.save
else
puts #record.errors.messages.inspect
#update your existing record instead.
end
Then in your model:
validate :my_validation
def my_validation
if record_already_exists
return errors.add :base, "Your custom error message here."
end
end
I'd recommend using #find_or_initialize_by or #find_or_create_by to instantiate your instances. Instead of placing record swapping code inside a callback. This means you'll do something like this (example controller create):
class Post < ApplicationController
def create
#post = Post.find_or_initialize_by(title: param[:title])
if #post.update(post_params)
redirect_to #post
else
render :new
end
end
end
Pair this with a validation that doesn't allow you to create double records with similar attributes and you're set.
create_table :posts do |t|
t.string :title, null: false
t.text :body
end
add_index :posts, :title, unique: true
class Post < ApplicationRecord
validates :title, presence: true, uniqueness: true
end
I don't recommend the following code, but you could set the id of your instance to match the record with similar data. However you'll have to bypass persistence (keeps track of new and persistent records) and dirty (keeps track of attribute changes). Otherwise you'll create a new record or update the current id instead of the similar record id:
class Post < ApplicationRecord
before_save :set_similar_id
private
def set_similar_id
similar_record = Post.find_by(title: title)
return unless similar_record
#attributes['id'].instance_variable_set :#value, similar_record.id
#new_record = false
end
end
Keep in mind that only changes are submitted to the database when creating a new record. For new records these are only the attributes of which the attributes are set, attributes with value nil are not submitted and will keep their old value.
For existing records theses are the attributes that are not the same as there older variant and the rule old_value != new_value (not actual variable names) applies.

Update another column if specific column exist on updation

I have a model which have two columns admin_approved and approval_date. Admin update admin_approved by using activeadmin. I want when admin update this column approval_date also update by current_time.
I cant understand how I do this.Which call_back I use.
#app/models/model.rb
class Model < ActiveRecord::Base
before_update 'self.approval_date = Time.now', if: "admin_approved?"
end
This assumes you have admin_approved (bool) and approval_date (datetime) in your table.
The way it works is to use a string to evaluate whether the admin_approved attribute is "true" before update. If it is, it sets the approval_date to the current time.
Use after_save callback inside your model.
It would be something like this:
after_save do
if admin_approved_changed?
self.approval_date = Time.now
save!
end
end
Or change the condition as you like!
You could set the approval_date before your model instance will be saved. So you save a database write process instead of usage of after_save where you save your instance and in the after_save callback you would save it again.
class MyModel < ActiveRecord::Base
before_save :set_approval_date
# ... your model code ...
private
def set_approval_date
if admin_approved_changed?
self.approval_date = Time.now
end
end
end
May be in your controller:
my_instance = MyModel.find(params[:id])
my_instance.admin_approved = true
my_instance.save

How do I set attributes in the Model?

I want to auto generate a hash value from within the model.
The user creates a resume, they then have the option to share it by clicking a share button which auto generates a unique (random hashed string) url tied to that specific resumes view.
class ResumesController < ApplicationController
def share
#resume = Resume.find(params[:id])
#share = Share.new
#share.resume_id = #resume.id
#share.save
redirect_to action: 'index'
end
end
My Share model has two columns, resume_id, which I already set in the controller, andhash_url` which I want to automatically set in the model.
class Share < ActiveRecord::Base
attr_accessible :label, :resume_id, :url
end
My question is, how do I go about creating a unique hash and store it in the hash_url column? Also, I'm assuming before it saves it will have to check the share table to make sure it is not saving a hash that already exists.
You can generate and store a hash before saving the object. Add something like this to your model:
# share.rb
before_validation :generate_hash
private
def generate_hash
self.hash_url = Resume.find(resume_id).content.hash
end
The hash method is a method Ruby provides: http://ruby-doc.org/core-2.1.1/String.html#method-i-hash It returns a hash based on the string's length and content.
before_create
I'm guessing you want to send users to the likes of:
domain.com/resumes/your_secret_hash_url #-> kind of like a uuid?
The way I would do this is to use the before_create callback with SecureRandom. Whilst this won't give you a unique value, you can check it against the form:
#app/models/resume.rb
Class Resume < ActiveRecord::Base
before_create :set_hash
private
def set_hash
self.hash_url = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless Resume.exists?(token: random_token)
end
end
end
Reference: Best way to create unique token in Rails?
This will give you the ability to set the hash_url on create, and have it be unique

Rails 3 add virtual attribute dynamically

My setup: Rails 3.0.9, Ruby 1.9.2
I have my reasons for doing this but what I need is a way to add a virtual attribute to an activerecord resultset dynamically. That means I am not using attr_accessor in the model and instead wish to dynamically add virtual attributes to a resultset.
For example,
users = User.all
#a user has following attributes: name, email, password
What I like to do is say add (without using attr_accessor) a virtual attribute status to users, is that possible?
You should do this:
users.each do |user|
user.instance_eval do
def status
instance_variable_get("#status")
end
def status=(val)
instance_variable_set("#status",val)
end
end
end
you can do the following:
add an attribute "extras" which will be accessed as a Hash, and which will store any additional / dynamic attributes -- and then tell Rails to persist that Hash via JSON in ActiveRecord or Mongo or whatever you use
e.g.:
class AddExtrasToUsers < ActiveRecord::Migration
def self.up
add_column :users, :extras, :text # do not use a binary type here! (rails bug)
end
...
end
then in the model add a statement to "serialize" that new attribute -- e.g. that means it's persisted as JSON
class User < ActiveRecord::Base
...
serialize :extras
...
end
You can now do this:
u = User.find 3
u.extras[:status] = 'valid'
u.save
You can also add a bit of magic to the User model, to look at the extras Hash if it gets a method_missing() call
See also:
Google "Rails 3 serialize"
If your attributes are read-only, you can also add them by selecting them in your database query. Fields which appear in the SQL result result will automatically be add as attributes.
Examples:
User.find_by_sql('users.*, (...) AS status')
User.select('users.*, joined_table.status').join(...)
With these examples you'll have all User attributes and an additional status attribute.
You could simply do:
users.each { |user| user.class_eval { attr_accessor :status } }
All users will have user.status and user.status = :new_status available.

Resources