I am new to Ruby-on-Rails 4. I have created a custom validator but cannot assign value to an array. It shows error
undefined method <<' for nil:NilClass.
It highlights #msg << 1
For instance, my model is like
class User < ActiveRecord::Base
has_secure_password
validates :email,:email_format => true, :on => :create
validates :password, password_format:{with: "upercase"}
end
My custom validator
class PasswordFormatValidator < ActiveModel::EachValidator
#def initilize(options)-Fixed
def initialize(options)
#msg=[]
#password1 = options[:attributes=>[:password]]
#val=options.inspect
super
end
def validate_each(record, attribute, value)
record.errors[attribute] << #val
unless (value.nil? || value.empty?)
#msg << 1
#record.errors[attribute] << "testing"
end
end
end
#val output
{:attributes=>[:password], :complexity=>3, :length=>6, :class=>User(id: integer, email: string, password_digest: string, created_at: datetime, updated_at: datetime)}
You have a typo in your constructor name, it should be initialize and not initilize. This is why your #msg variable is undefined - your constructor has never been called!
Have a nice day.
Related
I have 2 models named Drug and Frequency.
#app/models/frequency.rb
class Frequency
validates :code, presence: true
end
#app/models/drug.rb
class DrugValidator < ActiveModel::Validator
def validate(record)
drug_attributes = {name: record.name}
if Drug.where(name: record.name).any?
record.errors[:base] << "The drug #{record.name} already exists in system."
end
end
end
class Drug
validates_with DrugValidator, if: (lambda {|drug| drug.name.present?})
end
> pf = Frequency.find_or_create_by(code: 'Q24')
Frequency Load (0.4ms) SELECT "frequencies".* FROM "frequencies" WHERE "frequencies"."code" = $1 LIMIT 1 [["code", "Q24"]]
=> #<Frequency id: 7, code: "Q24", created_at: "2015-11-02 03:41:34", updated_at: "2015-11-02 03:41:34">
> pf.persisted?
=> true
> pf.valid?
=> true
> pf.new_record?
=> false
> pd = Drug.find_or_create_by(name: 'SomeDrug', unit: 'tablet')
=> #<Drug id: 10, name: "SomeDrug", created_at: "2015-11-05 07:42:46", updated_at: "2015-11-05 07:42:46", unit: "tablet">
> pd.persisted?
=> true
> pd.new_record?
=> false
> pd.valid?
=> false
> pd.errors.messages
=> {:base=>["The drug SomeDrug already exists in system."]}
the custom DrugValidator is executed even when fetching the record from the system. How do i avoid this for find_or_create_by?
Specify when this validation is active.
class Drug
validates_with DrugValidator, if: (lambda {|drug| drug.name.present?}), on: :create
end
I am delivering the mail message from
class DelayMessageJob < ActiveJob::Base
queue_as :default
def perform(*args)
message = self.arguments.first
MessageMailer.default(message).deliver_now!
message[:sent] = true
message.save
end
end
it's running fine until there... scheduled then enqueued
my MessageMailer class is quite simple
class MessageMailer < ApplicationMailer
def default(message)
#content = message.text
mail(
to: "#{message[:recipient_email]}",
subject: "Hi and welcome"
)
end
end
and it's parent ApplicationMailer
class ApplicationMailer < ActionMailer::Base
default from: "from#example.com"
layout 'mailer'
end
debugging the MessageMailer class, I can get the message arg ..
#<Message id: 19, recipient_email: "yves#icloud.com", text: "retesting",
delay_until_time: "2015-07-29 15:14:00", timezone_offset: 2,
sent: false, created_at: "2015-07-29 15:13:48",
updated_at: "2015-07-29 15:13:48"
mail is raising: TypeError: no implicit conversion of Message into Hash
UPDATE 1
I changed message[:sent] to message.sent, same error
8:06:17 sidekiq.1 | 2015-07-30T06:06:17.365Z 4871 TID-oxt3zobs8
WARN: {
"class"=>"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
"wrapped"=>"DelayMessageJob",
"queue"=>"default", "args"=> [{
"job_class"=>"DelayMessageJob",
"job_id"=>"9f1b52c0-bb4a-479c-bb5c-5ca11ae257b0",
"queue_name"=>"default",
"arguments"=>[{"_aj_globalid"=>"gid://delay-message-on-rails/Message/23"}]
}],
"retry"=>true,
"jid"=>"8cbffb19c9d88c48e9b9aa00",
"created_at"=>1438236297.952947,
"enqueued_at"=>1438236377.2572238,
"error_message"=>"no implicit conversion of Message into Hash",
"error_class"=>"TypeError",
"failed_at"=>1438236377.3641548, "retry_count"=>0
}
08:06:17 sidekiq.1 | 2015-07-30T06:06:17.365Z 4871 TID-oxt3zobs8
WARN: TypeError: no implicit conversion of Message into Hash
the error seems to be raised upon message being enqueued ...
As I want to send emails in background, using sidekiq, I should not forget to start sidekiq 'mailers' queue in my Procfile
sidekiq: sidekiq -q devise,1 -q default -q mailers
I can send delayed notifications using directly ActionMailer or using ActiveJob
1- using ActionMailer
controllers/messages_controler.rb
MessageMailer.keep_inform(#message).deliver_later(wait_until: #message[:delay_until_time])
mailers/message_mailer.rb
class MessageMailer < ApplicationMailer
def keep_inform(notification)
#content = notification.text
mail(
to: "#{notification.recipient_email}",
subject: "Hi and welcome"
)
end
end
2- using ActiveJobs
controllers/messages_controler.rb
DelayMessageJob.set(wait_until: #message[:delay_until_time]).perform_later(#message)
jobs/delay_message_jobs.rb
class DelayMessageJob < ActiveJob::Base
queue_as :default
def perform(notification)
MessageMailer.keep_inform(notification).deliver_now
notification.sent = true
notification.save
end
end
In both case :
I defined my mailers/messages_mailer.rb
class MessageMailer < ApplicationMailer
def keep_inform(notification)
#content = notification.text
mail(
to: "#{notification.recipient_email}",
subject: "Hi and welcome"
)
end
end
NOTE:
I don't know if it's important, but I changed the term 'message' to 'notification', I guess 'message' is used internally ...
I'd like to update a database record using where method.
This how I proceed:
def AjoutAuPanier
#book = Book.find(params[:id])
if #book.nbr_exemplaires > 0
#p = Panier.where(user_id: current_user, book_id: #book.id)
if #p.empty? == false
#p.update(#p.id, quantity: #p.quantity + 1)
else
#p = Panier.new(user_id: current_user.id , book_id: #book.id , price: #book.price, quantity: 1)
end
if #p.save
#book.update(nbr_exemplaires: #book.nbr_exemplaires-1)
redirect_to detail_path
else
redirect_to books_list_path
end
else
flash[:notice]='Ce livre n\'est plus disponible'
redirect_to books_list_path
end
end
This, however, gives the following error:
undefined method `id' for #
#p=Panier.where(user_id: current_user, book_id: #book.id)
You must add .first because .where always return an array
#p=Panier.where(user_id: current_user, book_id: #book.id).first
I'm using counter_culture to create survey applications
the problem is each time I add citizen the count columns is not automatically update
I have to go to console and run Citizen.counter_culture_fix_counts
below is my model and controller for reference
I'm using rails 4 and nested_attributes
thank you for help
model
class Familycard < ActiveRecord::Base
has_many :citizens , :dependent => :destroy
accepts_nested_attributes_for :citizens, :allow_destroy => :true
end
class Citizen < ActiveRecord::Base
belongs_to :familycard
counter_culture :familycard,
:column_name => Proc.new { |model| "#{model.sex}_count"},
:column_names => {
["citizens.sex = ? ", 'male'] => 'males_count',
["citizens.sex = ? ", 'female'] => 'females_count'
}
counter_culture :familycard
counter_culture :familycard,
:column_name => Proc.new { |model| "#{model.job}_count"},
:column_names => {
["citizens.job = ? ", 'Entrepreneur'] => 'Entrepreneurs_count',
["citizens.job = ? ", 'House wife'] => 'housewifes_count',
["citizens.job = ? ", 'Student'] => 'students_count',
["citizens.job = ? ", 'Veteran'] => 'veterans_count',
}
end
controller
class FamilycardController < ApplicationController
def new
#familycard = Familycard.new(:citizens => [Citizen.new])
end
def create
#familycard = Familycard.new(familycard_params)
if #familycard.save
flash[:success] = "Data Saved"
redirect_to familycards_path
else
render 'familycards/familycard_form'
end
end
follow up some comments from my question, I have solved my problem above, and below are sample code for condition for the gem
counter_culture :parent_model, :column_name => Proc.new {|child_model|
if child_model.published_condition == 'CONDITION 1'
"condition1_count"
elsif child_model.published_condition == 'CONDITION 2'
"condition2_count"
elsif child_model.published_condition == 'CONDITION 3'
"condition3_count"
end
}, :column_names => {
["child_models.published_condition = ?", 'CONDITION 1'] => 'condition1_count',
["child_models.published_condition = ?", 'CONDITION 2'] => 'condition2_count',
["child_models.published_condition = ?", 'CONDITION 3'] => 'condition3_count'
}
explanation:
parent_model has 3 fields to save the total number which are condition1_count,condition2_count and condition3_count
I have the following models set up
# task.rb
class Task << AR
# everything all task objects have in common
end
# login_request.rb
class Tasks::LoginRequest < Task
store :data, accessors: [:email, :first_name, :last_name, :expires_at]
composed_of :valid_until, class_name: 'DateTime', mapping: %w(expires_at to_s), constructor: Proc.new { |date| (date && date.to_datetime) || DateTime.now }, converter: Proc.new { |value| value.to_s.to_datetime }
end
I'm using the datetime_select helper in my form:
# _form.html.haml
= f.datetime_select :valid_until
This works quite well, but when I call update in my controller with the submitted form data I get the following error message:
1 error(s) on assignment of multiparameter attributes [error on assignment [2014, 4, 2, 9, 48] to valid_until (can't write unknown attribute 'expires_at')]
So, I'm guessing the updated method tries to manipulate the attributes hash directly, but obviously it can't find the attribute expires_at, since it's a simple accessor method of the JSON column data.
I know I could simply add this field to the DB and it would probably work - although there's no need then to have a composed_of statement. But I'd rather not go this route, because not every task has a expires_at column.
How can I overcome this error? Or did I miss something?
Currently compose_of is not supporting this scenario since it writes directly to attributes that are assumed to be in the database. I wrote a tweaked compose_of version that does (based of Rails 4.0.2 version)
Putting this in initialize folder:
#/initialize/support_store_in_composed_of.rb
module ActiveRecord
module Aggregations
extend ActiveSupport::Concern
def clear_aggregation_cache #:nodoc:
#aggregation_cache.clear if persisted?
end
module ClassMethods
def composed_of_with_store_support(part_id, options = {})
options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter, :store)
name = part_id.id2name
class_name = options[:class_name] || name.camelize
mapping = options[:mapping] || [ name, name ]
mapping = [ mapping ] unless mapping.first.is_a?(Array)
allow_nil = options[:allow_nil] || false
constructor = options[:constructor] || :new
converter = options[:converter]
reader_method(name, class_name, mapping, allow_nil, constructor, options[:store])
writer_method(name, class_name, mapping, allow_nil, converter, options[:store])
create_reflection(:composed_of, part_id, nil, options, self)
end
private
def reader_method(name, class_name, mapping, allow_nil, constructor, store=nil)
define_method(name) do
if #aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
if store.present?
attrs = mapping.collect {|pair| send(pair.first)}
else
attrs = mapping.collect {|pair| read_attribute(pair.first)}
end
object = constructor.respond_to?(:call) ?
constructor.call(*attrs) :
class_name.constantize.send(constructor, *attrs)
#aggregation_cache[name] = object
end
#aggregation_cache[name]
end
end
def writer_method(name, class_name, mapping, allow_nil, converter, store=nil)
define_method("#{name}=") do |part|
klass = class_name.constantize
unless part.is_a?(klass) || converter.nil? || part.nil?
part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
end
if part.nil? && allow_nil
mapping.each { |pair| self[pair.first] = nil }
#aggregation_cache[name] = nil
else
if store.present?
mapping.each { |pair| send("#{pair.first}=", part.send(pair.last)) }
else
mapping.each { |pair| self[pair.first] = part.send(pair.last) }
end
#aggregation_cache[name] = part.freeze
end
end
end
end
end
end
And using it like this would solve your problem.
class Task < ActiveRecord::Base
store :data, accessors: [:email, :first_name, :last_name, :expires_at]
composed_of_with_store_support :valid_until, class_name: 'DateTime', mapping: %w(expires_at to_s),
constructor: Proc.new { |date| (date && date.to_datetime) || DateTime.now },
converter: Proc.new { |value| value.to_s.to_datetime },
store: true
end