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.
Related
Im using Rails 5. I have a Product model defined as follows
class Product < ActiveRecord::Base
validates :image_url, presence: true
def image_url
super || 'http://someplaceholderimage.com'
end
end
Every product has an image_url attribute. Now, when i run the below query:
Product.all.where.not(image_url: nil)
im expecting it to return all products even if the image_url was nil in the database for any of them. But the default value i defined in the model is not being used and im getting only those products whose image_url column has a non nil value in the database. How can i fix this ?
Thanks
That's an instance method. It'll work on any instance of the Product class.
Every single query made to your database through the models is a class method. So they're not going to work as you expected.
What you can do is to add a default value to that column in your database. So, whenever a record is inserted, with nil (NULL) image_url, it'll take the value you defined as default.
It also exists the possibility to add a default value using the SQL CASE expression, which adds more flexibility regarding the need you have:
Product.select("CASE WHEN image_url IS NULL
THEN 'http://someplaceholderimage.com'
ELSE image_url END, *")
(this is PostgreSQL syntax)
Combining both ways you won't have to add more code to your model.
For changing the default value of the column, you can use change_column_default:
class ChangeImageUrlDefaultProducts < ActiveRecord::Migration[6.0]
def change
change_column_default :products, :image_url, from: nil, to: 'http://someplaceholderimage.com'
end
end
This is reversible by using the from and to option arguments.
In Rails .where() is structuring some SQL to be called on the database. So the code is doing what you asked: it's going to the database and asking for all records where image_url is not nil.
What you are defining on the model is a method that:
looks for an image_url in the database
returns it if there is one
returns 'http://someplaceholderimage.com' if there isn't one
To achieve your stated goal:
im expecting it to return all products even if the image_url was nil in the database
You simply need to call Product.all
I'm not sure why you would add .where.not() if you didn't want that to be a condition.
To really drive the point home: Expecting the database to return records where :image_url is either nil or not nil:
Product.where(image_url: nil).or(Product.where.not(image_url: nil)) is the same as calling Product.all
If you want to make sure every record has a URL defined in the database, you could do a before_validation callback in the Model:
class Product < ActiveRecord::Base
validates :image_url, presence: true
before_save :default_image_url, if: ->{ image_url.blank? }
# def image_url
# super || 'http://someplaceholderimage.com'
# end
private
def default_image_url
image_url ||= 'http://someplaceholderimage.com'
end
end
If you want to be able to tell which records are returning the default :image_url and which records have an :image_url saved in the database, you need to rename the method so it doesn't conflict with the database column name:
class Product < ActiveRecord::Base
validates :image_url, presence: true
def image
image_url || 'http://someplaceholderimage.com'
end
end
Now calling #product.image_url will return whatever is in the database and calling #product.image will either return the dB value or the placeholder value.
But, there's another issue:
Your validates :image_url, presence: true means the record will never save if the :image_url is blank.
So asking the database to look for a potentially nil value when you've already ensured that every record can't have a nil value is a bit of a head-scratcher.
Final tangent:
If you must have the method name and database column name match, I don't think super is a good option.
I think you should use this:
def image_url
read_attribute(:image_url) || 'http://someplaceholderimage.com'
end
If nothing else, it's a bit more clear than super as to what is happening.
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
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
In a project came across a requirement wherein a logged in user should be asked specific data based on his company. This specific data would be company specific, and could be mandatory or unique. This is the approach I took.
1. Defined a model to with three fields: Label (string), Mandatory(boolean), Unique(boolean).
2. The admin of the company could then enter the required fields for. e.g: Label => "Employee number", Mandatory => true, Unique => false using a simple form.
3. This data should be asked at the time of creating another record of model Redeemed Coupon for logged in user.
4. So during initialize of the Redeemed Coupon model, reopening the class, and checking the logged in user's company.
class RedeemedCoupon
def initialize(attrs = nil, options = nil)
super
if Merchant.current #this is set in the application controller as thread specific variable
coupon_custom_field = CouponCustomField.where(:merchant_id => Merchant.current).first
if coupon_custom_field and coupon_custom_field.custom_fields.size > 0
coupon_custom_field.custom_fields.each do |custom_field|
class_eval do
field custom_field.label.to_sym, :type => String
attr_accessible custom_field.label.to_sym
end
if custom_field.unique
class_eval do
index custom_field.label.to_sym
#validates_uniqueness_of custom_field.label.to_sym, :case_sensitive => false
end
end
if custom_field.mandatory
class_eval do
#validates_presence_of custom_field.label.to_sym
end
end
end
end
end
end
However the validations validates presence of and uniqueness does not work, with a failure message being given : callback not defined. this is thrown before save, when is_valid? is called object.
TO work around that
Put in custom validation
validate :custom_mandatory_unique
def custom_mandatory_unique
if Merchant.current
coupon_custom_field = CouponCustomField.where(:ira_merchant_id => Merchant.current).first
if coupon_custom_field and coupon_custom_field.custom_fields.size > 0
coupon_custom_field.custom_fields.each do |custom_field|
field_value = self.send(custom_field.label.to_sym)
self.errors.add(custom_field.label.to_sym, "cannot be blank") if !field_value.present? and custom_field.mandatory
if field_value.present? and custom_field.unique
if RedeemedCoupon.where(custom_field.label.to_sym => field_value, :merchant_id => Merchant.current).size > 0
self.errors.add(custom_field.label.to_sym, "already taken")
end
end
end
end
end
end
My questions:
1. Is this the best way of doing it.
2. Are there any gems already present (have searched, however couldnt get any)?
3. How can i use the validation helpers here instead of defining a seperate validate block?
I would define a model that stores the set of attribute mappings that correspond to a company, and an attribute model that holds its values and is associated with your coupon model. Then create a custom validation method in coupon that makes sure all of the require attributes are present based on the company id, and a method that builds them per the company association.
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.