I've got below code in my model which I want to use for validation:
class Payslip < ActiveRecord::Base
include ActiveModel::Validations
attr_accessor :first_name, :last_name, :annual_salary, :super, :payment_start_date
validates :annual_salary, :super, numericality: { only_integer: true },
presence: true
validates :super, inclusion: { in: 0..50 }
validates :first_name, :last_name, presence: true,
length: { maximum: 100 }
validates_date :payment_start_date
validates :payment_start_date, presence: true
end
I have CSV import from the form and this gets passed over to concern:
module CSV_Manager
extend ActiveSupport::Concern
class << self
def extract_csv(csv_file, headers)
results = []
CSV.foreach(csv_file.path, {headers: false, :encoding => 'UTF-8'}) do |row|
data = row.split(',')
Payslip.first_name = data[0]
Payslip.last_name = data[1]
Payslip.super = data[2]
results.push(row) unless $. == headers.to_i
end
return results
end
def prepare_csv(rows, headers)
csv_file = CSV.generate do |csv|
csv << headers
rows.map { |row| csv << row }
end
return csv_file
end
end
end
I've added Payslip.first_name etc in an attempt to validate and throw an error if not validated.
Is this the right approach?
Here's a rough suggestion on how I would handle the problem you're trying to solve. Feel free to comment if this isn't what you're looking for:
def extract_csv(csv_file, headers)
results = []
CSV.foreach(csv_file.path, {headers: false, :encoding => 'UTF-8'}) do |row|
data = row.split(',')
payslip = Payslip.create({
first_name: data[0],
last_name: data[1],
super: data[2]
})
results.push(row) unless $. == headers.to_i
end
return results
end
Also, super is a reserved keyword, so I would suggest possibly not using it if you an avoid it.
Related
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.
I have set up a CSV import for my Ruby App. Everything works fairly well but I'm running into problems when I try to upload a string that then searches for an id to input as the foreign key in the hash. My model for the CSV looks as follows:
class Player < ActiveRecord::Base
belongs_to :team
validates :team_id, presence: true
def self.import(file)
CSV.foreach(file.path, headers: true, :header_converters => :symbol) do |row|
player_hash = row.to_hash
teamname = player_hash[:team]
teamhash = Team.where(:name => teamname).first
hashid = teamhash.id
player_newhash = player_hash.reject!{ |k| k == :team}
player_newhash[:team_id] = hashid
end
Player.create! (player_newhash)
end
end
I'm sure this is where the problem lies. When trying to execute I get the error:
undefined local variable or method `player_newhash' for #
Any help would be greatly appreciated.
player_newhash is a local variable inside your foreach loop - so your create would need to be in there as well:
class Player < ActiveRecord::Base
belongs_to :team
validates :team_id, presence: true
def self.import(file)
CSV.foreach(file.path, headers: true, :header_converters => :symbol) do |row|
player_hash = row.to_hash
teamname = player_hash[:team]
teamhash = Team.where(:name => teamname).first
hashid = teamhash.id
player_newhash = player_hash.reject!{ |k| k == :team}
player_newhash[:team_id] = hashid
Player.create!(player_newhash)
end
end
end
BTW - I got that answer by refactoring for a second to find out what was going on... in case it's helpful my version looked like this:
class Player < ActiveRecord::Base
belongs_to :team
validates :team_id, presence: true
def self.import(file)
CSV.foreach(file.path, headers: true, :header_converters => :symbol) do |row|
player_hash = row.to_hash
player_newhash = player_hash.reject!{ |k| k == :team}
player_newhash[:team_id] = find_team(player_hash[:team]).id
Player.create!(player_newhash)
end
end
private
def find_team(team_name)
Team.where(name: team_name).first
end
end
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
lesson.rb:
class Lesson < ActiveRecord::Base
before_save :create_permalink
before_create :append_order
validates :name, presence: true, length: { maximum: 50 },
uniqueness: { case_sensitive: false }
.
.
def to_param
permalink
end
private
def create_permalink
link = self.name.dup
replacements = [ ["\'", ""], [" ", "-"], ["!", ""] ]
replacements.each {|replacement| link.gsub!(replacement[0], replacement[1])}
self.permalink = link.downcase
end
def append_order
self.order = self.order.to_s + '-' + self.permalink
end
end
When I create a new lesson the order is only saving as an integer without appending the permalink on the end. I have made the create_permalink function get called before append_order so it would have something to append but this still doesn't work.
How would I make this work?
when you call self.permalink in your append_order method it has not been persisted yet or was not passed as a param into your model, thus it's still nil, to see if it works try calling self.update_attribute(:permalink, value) inside you create_permalink method or move the logic from append_order to create_permalink to avoid an extra db operation
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.