Is possible to use Rails ActiveModel::Validations per instance? - ruby-on-rails

theres an excerpt of my code:
module Configuracao
extend self
class Key
include ActiveModel::Validations
attr_accessor :name, :type, :default, :validations, :group, :available_values
def initialize(params)
params.symbolize_keys!.assert_valid_keys(:name, :type, :default, :validations, :group, :available_values)
#group = params[:group]
#name = params[:name]
#type = params[:type]
#available_values = params[:available_values]
#default = params[:default]
#validations = params[:validations]
#in this way each validation is being added for all keys
Configuracao::Key.class_eval do
validates :value, params[:validations]
end
end
end
end
so for every instance key i will have a diferent validation passed in a hash, example:
Key.new( validations: { presence: true, numericality: true } )
Key.new( validations: { length: { maximum: 30 } } )
There's a way to do it?

well i found a solution, maybe not so elegant or best way to do, but it works
def initialize(params)
params.symbolize_keys!.assert_valid_keys(:name, :type, :default, :validations, :group, :available_values)
#group = params[:group]
#name = params[:name]
#type = params[:type]
#available_values = params[:available_values]
#default = params[:default]
##current_validations = nil
##current_validations = #validations = params[:validations]
class << self
validates :value, ##current_validations unless ##current_validations.blank?
end
end
now each time i instantiate a Key, the class will be modified only for that instance

Will this work?
...
validates :all_hash_validations_pass
...
def all_hash_validations_pass
...iterate through the hash here, and validate each of them
end
If not, you should be able to use a custom validator for more control.

Related

Rails : Wrong number of arguments (given 1, expected 0)

I get this error on my posts index page :
This the model :
class Post < ApplicationRecord
include Filterable
belongs_to :region
belongs_to :category
belongs_to :topic
validates :title, presence: true, length: { maximum: 500 }
validates :content, presence: true
validates :published_at, presence: true
translates :title, :content, :slug, touch: true, fallbacks_for_empty_translations: true
has_attached_file :image, styles: { thumb: "100x70#", featured: "1560x868#", small: "760x868#", big: ">1600x1600" }
validates_attachment :image, content_type: { content_type: ["image/jpeg", "image/gif", "image/png"] }
validates_attachment_presence :image
scope :published, -> (published) { where(published: (['true', true].include? published)).order(featured: :desc, published_at: :desc) }
scope :published_until_now, -> { where("published_at < ?", Time.now).merge(Post.published(true)) }
scope :topic, -> (topic_id) {
joins(:topic).where('topic_id = ?', topic_id) }
scope :category, -> (post_category) {
joins(:category).where('category_id = ?', post_category) }
scope :match, -> (search_term) {
with_translations(I18n.locale).where('content like ? or title like ?', "%#{search_term}%", "%#{search_term}%") }
self.per_page = 10
after_save :unfeature_older_posts, if: Proc.new { |post| post.featured? }
extend FriendlyId
friendly_id :title, use: :globalize
def unfeature_older_posts
featured_posts = Post.where(featured: true).where.not(id: id).order(published_at: :desc)
if featured_posts.size == 1
featured_posts.last.update(featured: false)
end
end
end
This the controller :
class PostsController < ApplicationController
before_action :get_pages_tree, :get_privacy_policy, only: [:index, :show]
def index
#filters = params.slice(:topic, :category)
#posts = Post.published_until_now
.filter(#filters)
.paginate(:page => params[:page], per_page: 11)
end
def show
#post = Post.friendly.find(params[:id])
end
end
and filter is defined here :
module Filterable
extend ActiveSupport::Concern
module ClassMethods
def filter(filtering_params)
results = self.where(nil)
filtering_params.each do |key, value|
results = results.public_send(key, value) if value.present?
end
results
end
end
end
I'm not sure where to go from here. I recently upgraded to Ruby on Rails 5 and Ruby 2.7.0, I don't know if it's related.
Try replacing module ClassMethods with class_methods do.
If it works, then please keep in mind:
filter method comes from Ruby. It's defined in Array. As you can see in the doc, filter method on Array takes no argument. That's the direct cause of the error you see.
In Rails, when methods on Array are called on ActiveRecord object (in your case, Post.published_until_now) and when methods cannot be found on a model, it automatically converts itself into an Array. So, it calls filter method on Array. Generally, you don't want to define methods such as filter which is confusing.

Rails Create new active record with association value passed in params

I have 2 rails models which look like this
class Physician < UserProfile
has_many :state_licenses, inverse_of: :physician, autosave: true, dependent: :destroy
validates :state_licenses, :length => { :minimum => 1, message: "Please select at-least one state license"}
class StateLicense < ApplicationRecord
include RailsAdminPhysicianDependencyConcern
belongs_to :physician, inverse_of: :state_licenses
belongs_to :state, optional: true
attr_accessor :client_id
validates :state, presence: { message: I18n.t("errors.choose_one", field: 'state') }
#validates :license_number, presence: { message: I18n.t("errors.blank") }
def name
return "" unless state
"#{state.try(:name)}"
end
end
In my controller, I am using the code below to create a new Physician record with a bunch of state licenses but for some reason, the state licenses I pass to the create function never make it to the Physician model
def create
physician = nil
ActiveRecord::Base.transaction do
state_licenses = params["state_licenses"]
state_licenses_For_Association = []
if (state_licenses != nil)
state_licenses.each do |state_license|
sl = {}
sl[:state_id] = state_license
state_licenses_For_Association.push(sl)
end
end
physician = Physician.create(params.permit(:first_name, :last_name, :title, :residency_board_status, :residency_specialty_id, :state_licenses => state_licenses_For_Association))
user_record = nil
super do |user|
user_record = user
user.errors.delete(:user_profile)
physician.errors.messages.each { |field, messages| messages.each {|message| user.errors.add(field, message)} }
end
raise ActiveRecord::Rollback unless user_record.persisted? && physician.persisted?
end
AdminNotificationsMailer.physician_signed_up(physician).deliver_now rescue nil
end
What am I doing wrong?
Try changing this:
physician = Physician.create(params.permit(:first_name, :last_name, :title, :residency_board_status, :residency_specialty_id, :state_licenses => state_licenses_For_Association))
to this:
physician = Physician.create(params.permit(:first_name, :last_name, :title, :residency_board_status, :residency_specialty_id).merge(state_licenses: state_licenses_For_Association)) # note the .merge call

Rails Conditional Validation in Model from Controller Session Data

I am trying to conditionally validate full_name and zip based on whether a visitor is part of a test (Visitors that are part of the test will have certain session data). I am able to pass true/false from the leads controller to the customer model via customer.visitor_test(), but I can't access #test from in_test? in the model. What am I missing?
customer.rb
/* Stripped down code */
class Customer < ActiveRecord::Base
attr_accessor :test
validates :full_name, presence: true, if: :not_in_test?
validates :zip, presence: true, if: :in_test?
def visitor_test(bool)
#test = bool
end
def in_test?
#test
end
def not_in_test?
!self.in_test?
end
end
leads_controller.rb
/* Stripped down code */
class LeadsController < ApplicationController
def create
session[:zip] = zip
session[:email] = email
session[:full_name] = full_name
session[:email_opt_in] = email_opt_in
session[:phone] = phone
listing = Listing.where(id: listing_id).first
customer = create_or_update_customer_from_session(listing)
customer.visitor_test(/* true || false */)
if customer.errors.blank?
/* Do something */
else
/* Something else */
end
end
end
/* Stripped down code */
class Customer < ActiveRecord::Base
attr_accessor :test
validates :full_name, presence: true, if: :not_in_test?
validates :zip, presence: true, if: :in_test?
def in_test?
test
end
def not_in_test?
!in_test?
end
end
attr_accessor provides setter and getter.
/* Stripped down code */
class LeadsController < ApplicationController
def create
session[:zip] = zip
session[:email] = email
session[:full_name] = full_name
session[:email_opt_in] = email_opt_in
session[:phone] = phone
listing = Listing.where(id: listing_id).first
customer = create_or_update_customer_from_session(listing) customer.test = true
customer.save
if customer.errors.blank?
/* Do something */
else
/* Something else */
end
end
end

Before Validation loop through self attributes for modification

I have created a simple before_validation:
before_validation :strip_tabs
def strip_tabs
end
In my class I want to loop through all my attributes and remove tabs from each value. Most posts I found on SO are people who want to set 1 attribute. But I want to edit all my values.
Question:
How can I loop all self attributes of a model and edit them.
Friend suggested this, but content_column_names does not exist:
self.content_column_names.each {|n| self[n] = self[n].squish}
UPDATE 1: More code:
class PersonalInfo
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
extend ActiveModel::Translation
extend ActiveModel::Callbacks
include Sappable
require 'ext/string'
attr_accessor \
:first_name, :middle_name, :last_name,:birthdate,:sex,
:telephone,:street,:house_number,:city,:postal_code,:country,
:e_mail, :nationality, :salutation, :com_lang
validates :e_mail, :email => {:strict_mode => true}
validate :validate_telephone_number
validate :age_is_min_17?
before_validation :strip_tabs
def strip_tabs
binding.remote_pry
end
def age_is_min_17?
birthdate_visible = PersonalField.not_hidden.find_by_name "BIRTHDATE"
if birthdate_visible && birthdate && birthdate > (Date.current - 17.years)
#errors.add(:birthdate, I18n.t("apply.errors.birthdate"))
end
end
def validate_telephone_number
telephone_visible = PersonalField.not_hidden.find_by_name "TELEPHONE"
telephone_current = telephone.dup
if telephone_visible && telephone_current && !telephone_current.empty?
if telephone_current[0] == '+' || telephone_current[0] == '0'
telephone_current[0] = ''
#errors.add(:telephone, I18n.t("apply.errors.telephone")) if !telephone_current.is_number?
else
#errors.add(:telephone, I18n.t("apply.errors.telephone"))
end
end
end
def initialize(hash)
simple_attributes = [:first_name, :middle_name, :last_name,:birthdate,:sex,
:telephone,:street,:house_number,:city,:postal_code,:country,
:e_mail, :nationality, :salutation, :com_lang]
simple_attributes.each do |attr|
set_attr_from_json(attr, hash)
end
set_attr_from_json(:birthdate, hash) {|date| Date.parse(date) rescue nil}
end
end
Update 2: Rails Version:
I'm using Rails '3.2.17'
You can do as following:
before_validation :strip_tabs
def strip_tabs
self.attributes.map do |column, value|
self[column] = value.squish.presence
end
end
But I think that .squish will not work on created_at, updated_at, id, ... Because they are not String!
def strip_tabs
self.attributes.map do |column, value|
self[column] = value.kind_of?(String) ? value.squish.presence : value
end
end
Since your class is not a Rails model (ActiveRecord::Base), you can do as following:
def strip_tabs
self.instance_variables.map do |attr|
value = self.instance_variable_get(attr)
value = value.squish if value.kind_of?(String)
self.instance_variable_set(attr, value)
end
end
This should work
def strip_tabs
self.attributes.each do |attr_name, attr_value|
modified_value = ... # calculate your modified value here
self.write_attribute attr_name, modified_value
end
end
Because it's not an ActiveRecord model you won't have attributes or column_names, but you already have an array of your attribute names in your initialize function. I would suggest making that into a constant so you can access it throughout the model:
class PersonalInfo
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
extend ActiveModel::Translation
extend ActiveModel::Callbacks
include Sappable
require 'ext/string'
SIMPLE_ATTRIBUTES = [:first_name, :middle_name, :last_name,:birthdate,:sex,
:telephone,:street,:house_number,:city,:postal_code,:country,
:e_mail, :nationality, :salutation, :com_lang]
attr_accessor *SIMPLE_ATTRIBUTES
before_validation :strip_tabs
def strip_tabs
SIMPLE_ATTRIBUTES.each{ |attr| self[attr] = self[attr].squish }
end
...
def initialize(hash)
SIMPLE_ATTRIBUTES.each do |attr|
set_attr_from_json(attr, hash)
end
set_attr_from_json(:birthdate, hash) {|date| Date.parse(date) rescue nil}
end
end

Validate uniqueness of globalized field

I have a model with translated fields using the globalize gem and globalize-accessors gem for providing localized attributes such as name_en, name_zh_hk for a localized name field.
for example:
class Person < ActiveRecord::Base
translates :name
globalize_accessors: locales: [:en, :"zh-HK"], attributes: [:name]
# problem is:
validates :name, presence: true, uniqueness: true
end
So now name_en and name_zh_hk can get and set the value in corresponding locale correctly.
However, the validates :name validates only the name field in Person model. I also want to validate the uniqueness of the chinese input.
In short, would like a (easy) way to validate uniqueness of both name_en and name_zh_hk
** I have a form to submit both name_en and name_hk.
At the end of your person.rb model file (outside of class Person ... end, add this:
Person::Translation.class_eval do
validates_presence_of :name
validates_uniqueness_of :name
end
You have to do this
class Person < ActiveRecord::Base
translates :name
class Translation
validates :name, presence: true, uniqueness: true
end
end
I could be confused what you are asking about unique scopes:
validates :name, uniqueness: {scope: :blah}
specifically you may want to have a "PersonName" model.
PersonName
name | local | person_id
Person has_many :names
then have:
validates :name, uniqueness: { scope: :person_id }
this way if they enter a name for HK the same as the name for :en it will not be valid.
Solved with the following code.
Model
# /app/models/category.rb
...
I18n.available_locales.each do |locale|
validates :"name_#{locale}", presence: true, length: { maximum: 5 }, uniqueness: true
end
Validator
# config/initializers/associated_translations_uniqueness_validator.rb
require 'active_record'
require 'active_record/validations/uniqueness.rb'
ActiveRecord::Validations::UniquenessValidator.class_eval do
def validate_each_with_associated_translations(record, attribute, value)
klass = record.class
if klass.translates? && !klass.translated?(attribute) && klass.globalize_attribute_names.include?(attribute)
attribute_parts = attribute.to_s.rpartition('_')
raw_attribute = attribute_parts.first.to_sym
locale = attribute_parts.last.to_sym
finder_class = klass.translation_class
table = finder_class.arel_table
relation = build_relation(finder_class, table, raw_attribute, value).and(table[:locale].eq(locale))
relation = relation.and(table[klass.reflect_on_association(:translations).foreign_key].not_eq(record.send(:id))) if record.persisted?
translated_scopes = Array(options[:scope]) & klass.translated_attribute_names
untranslated_scopes = Array(options[:scope]) - translated_scopes
untranslated_scopes.each do |scope_item|
scope_value = record.send(scope_item)
reflection = klass.reflect_on_association(scope_item)
if reflection
scope_value = record.send(reflection.foreign_key)
scope_item = reflection.foreign_key
end
relation = relation.and(find_finder_class_for(record).arel_table[scope_item].eq(scope_value))
end
translated_scopes.each do |scope_item|
scope_value = record.send(scope_item)
relation = relation.and(table[scope_item].eq(scope_value))
end
if klass.unscoped.with_translations.where(relation).exists?
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
end
else
validate_each_without_associated_translations(record, attribute, value)
end
end
alias_method_chain :validate_each, :associated_translations
end

Resources