i have a module where i can create polls, the admin user fills in a form the url of the survey and its expiration date, the goal is that if i already create 1 poll and the expiration date is not yet met do not allow me to create other one.
i run a simple scaffold like this
rails g scaffold poll name url expiration_date:date
thats all i got by now.
validates :poll_existence
def poll_existence
if self.expiration_date.present?
if self.expiration_date >= Date.today
errors.add(:base, "a message")
end
end
end
I tried with a validates method and its passing the conditions, but its working for all the actions, so it doesnt allow me to create any poll.
If i understand, your validation is if there is already one poll that it`s not expired, the admin cannot create another poll, alright?
If i were you i would do something like that:
Create a scope for ongoing polls:
class Poll < ApplicationRecord
scope :ongoing , -> { where('expiration_date > ?', Time.zone.now) }
end
For validations:
validates :poll_existence, on: :create
def poll_existence
if Poll.ongoing.exists?
errors.add(:base, "Already have a poll")
end
end
You are not checking other polls from the parent model.
Lets' assume the parent model is User
validate :poll_existence
def poll_existence
if self.expiration_date.present?
if self.user.polls.where("DATE(expiration_date) > DATE(?)", Date.today).any?
errors.add(:base, "a message")
end
end
end
should work
Related
I need to run a job that sends email to the user when a contest's field named published_at will be set. So I have a Contest model and a method that runs a job:
class Contest < ApplicationRecord
after_create :send_contest
private
def send_contest
SendContestJob.set(wait: 30.minutes).perform_later(self)
end
end
But the job will run even if published_at field is blank. Validating the field to be present is not an option because published_at can be set later. So are there any solutions how can I run the job after setting the field? Thanks ahead.
ActiveModel::Dirty might be useful here. With it, you can inspect what fields are about to change/have been changed:
person.name # => "bob"
person.name = 'robert'
person.save
person.previous_changes # => {"name" => ["bob", "robert"]}
So, say, if published_at_changed? returns true, you schedule the job.
Instead of using after_create you can use before_save which is fired both for new and existing records.
The if: and unless: options allow you to specify conditions that need to be met for a callback to be called, you can pass a Proc, Lambda or the name of a method to be called.
class Contest < ApplicationRecord
before_save :send_contest, if: -> { published_at.present? && published_at_changed? }
# or
before_save :send_contest, if: :publishable?
private
def send_contest
SendContestJob.set(wait: 30.minutes).perform_later(self)
end
def publishable?
published_at.present? && published_at_changed?
end
end
As recommended by Sergio Tulentsev you can use ActiveRecord::Dirty to check for changes to the value of the column. Be sure to read the docs carefully though as there are plenty of gotchas.
I'm trying to put a validation on a record. The validation will check that the record can't be created if the ip_address and post_id are the same. This works good.
I am trying to add another condition that will allow this duplication only if it is after a 24 hour period, if so, allow it to save, but all future saves will be disabled again until the 24 period is over.
Here has been my best attempt so far:
validates_uniqueness_of :ip_address, scope: :impressionable_id,
conditions: -> {where('created_at < ?', Time.now - 24.hours)}
So the validation should somehow only check the "latest" record of the group it finds to do the validation to be accurate
Thank you!
It might be easier to just make an explicit validator method for this:
class Foo < ActiveRecord::Base
validate :ip_address_uniqueness, on: :create
# ...
def ip_address_uniqueness
existing = Foo.where(impressionable_id: self.impressionable_id)
.where(ip_address: self.ip_address)
.where(created_at: Time.current.all_day)
errors.add(:ip_address, 'cannot be used again today') if existing
end
end
The user has a balance (user.balance).
The user can post bets on a game (bet.amount).
How could I stop the user from betting more than what is in their balance?
I assume I could create a validation that looks something like this?
def enough_funds?
#bet.bet_amount > self.current_user.balance
flash[:notice] = "You do not have the available funds for this bet"
end
I'm still new to rails, be gentle :)
You're on the right track:
class Bet < ActiveRecord::Base
belongs_to :user
validate :funds_suffiency
def funds_sufficiency
errors.add :bet_amount, "is more than your available balance" if bet_amount < user.balance
end
end
If Bet's :bet_amount is less than the related User's available :balance, the error will be added to :bet_amount attribute, invalidating the model instance.
I have an object with multiple validations.
gist of the Approval model: https://gist.github.com/1579150 (side note, I know the Email Domain Validor doesn't work...)
The point is, if these validations fail, I want the object to save, but then set a value on approval.issue = true. Approval.issue is a boolean field that defaults to false, but then if the object fails validations I want the system admin to be able to see it and then handle it appropriately.
To make it more idiot proof, it would be nice to have some validations that can force the user to make changes, but then some would be exempt and would simply trigger the .issue field to true.
For instance, if the email is of the right domain but the email doesn't exist in the system, it would save it but then set issue => true. I could then set up a simple view for Approvals where :issue => :true. then the admin could modify or delete bad Approvals.
Ideas?
Code from gist:
class Approval < ActiveRecord::Base
class ApproverEmailValidator < ActiveModel::EachValidator
def validate_each(approval, attribute, value)
approval.errors[attribute] << "must be a valid e-mail address in our system" unless is_valid_email?(value)
end
protected
def is_valid_email?(address)
User.find_by_email(address)
end
end # End Approver Validator
class EmailDomainValidator < ActiveModel::EachValidator
def email_domain_is?(domain)
unless /ravennainteractive.com$/ =~ email(domain)
errors.add(:email, "You must Use an Eddie Bauer email address")
end
end
end #End Email Domain Validator
belongs_to :recommendation
attr_accessible :approval, :email, :user_id
validates :email, :email_domain
validates :next_approver_email, :approver_email => { :if => :recently_approved? }
before_save :create_next_approval
after_create :approval_notification
attr_accessor :next_approver_email
def recently_approved?
self.approved_changed? && self.approved?
end
def create_next_approval
next_approval = self.recommendation.approvals.build(:email => self.next_approver_email, :user_id => User.find_by_email(next_approver_email))
next_approval.save if next_approver_email.present? && recently_approved?
end
def email_domain_is?
unless /ravennainteractive.com$/ =~ email
errors.add(:email, "You must Use an Eddie Bauer email address")
end
end
private
def approval_notification
ApprovalMailer.needs_approval(self).deliver
end
end
You can implement observer for Approval that will analyze you objects before saving and set issue to "true", if there is some suspicious input.
UPDATE: Here is short guide how to implement observer:
rails generate observer - after this step you`ll see _observer.rb file.
Implement needed methods. Here is simple example extracted from one of my projects (It seems like you should use "before_save" method):
class HomeworkObserver < ActiveRecord::Observer
def after_create(homework)
TeacherMailer.send_later(:student_submitted_homework, homework)
end
def after_save(homework)
if (homework.checked)
StudentMailer.send_later(:teacher_checked_homework, homework)
end
end
end
Also you need to enable observer by adding it to your config/application.rb, e.g:
config.active_record.observers = :homework_observer
Official docs: http://api.rubyonrails.org/classes/ActiveRecord/Observer.html
I'm looking to create a custom validation in Rails. I need to validate that a POSTed start_date_time and end_date_time (together) do not overlap that combination in the database.
Example:
In the database:
start_date
05/15/2000
end_date
05/30/2000
POSTed:
start_date
05/10/2000
end_date
05/20/2000
FAILS!
Here's the rub:
1) I want to send both start and end fields into the function
2) I want to get the values of both POSTed fields to use in building a query.
3) Bonus: I want to use a scope (like say, for a given [:user_id, :event] -- but, again, I want that to be passed in.
How do I get the values of the fields?
Let's say my function looks like this:
def self.validates_datetime_not_overlapping(start, finish, scope_attr=[], conf={})
config = {
:message => 'some default message'
}
config.update(conf)
# Now what?
end
I'm sort of stuck at this point. I've scoured the net, and can't figure it out.... I can get the value of either start or finish, but not both at the same time by using validate_each ...
Any help would be great!
Thanks :)
What about custom validation methods?
You can add:
validate :check_dates
def check_dates
do.whatever.you.want.with.any.field
end
EDIT:
So maybe validate_with?
Modified example from RoR Guides:
class Person < ActiveRecord::Base
validates_with DatesValidator, :start => :your_start_date, :stop => :your_stop_date
end
class DatesValidator < ActiveRecord::Validator
def validate
start = record.send(options[:start])
stop = record.send(options[:stop])
...
end
end