validates :place_id, :title, :level, :start_at, :end_at, :presence => true
validate :event_takes_place_in_one_day, :event_is_not_in_past
def event_takes_place_in_one_day
binding.pry
self.start_at.day == self.end_at
end
and bang! error, when start_at or end_at is nil.
undefined method `to_datetime' for nil:NilClass
-
app/models/meeting.rb:22:in `cover?'
app/models/meeting.rb:22:in `event_is_not_in_past'
app/controllers/meetings_controller.rb:18:in `create'
Afaik it is also possible to implemnt this with custom validator, but I suppose this is not the case, because of there is only one validator per class, so I should do three classes for 3 checks, isn't it?
P.s.
Application have a Meeting model with start_at and end_at DateTime type
attributes, which indicate a start and the end of the meeting
respectively.
I need to implement next logic
start_at < end_at (that it starts after than end)
start_at.day == end_at.day (meeting ends within one day)
and also meeting can be created only for the current month.
Will a nil check work for you?
def event_takes_place_in_one_day
#only execute if start_at & end_at is not nil
unless self.start_at.nil? || self.end_at.nil?
binding.pry
self.start_at.day == self.end_at
end
end
Related
In my app, I have several clients, and they have several elements (via has_many_through association) depending on a certain BusinessType to which Client belongs to so that instead of manually adding all the elements to the Client, I can just select the BusinessType and everything gets added automatically (business_type in Client is attr_readonly). BusinessType HABTM elements.
Here's the catch, after creation with the default BusinessType, the clients can update their elements and remove or add as they please (mostly add), so what I'm trying to do is the following:
Suppose one business_type has elements [1,2,3] and is assigned to one client, then, the following elements are added manually to the client = [4,5,6] so it ends up having [1,2,3,4,5,6], ok everything's fine here.
But after this, the business_type gets updated and has element 2 removed, so it ends up being [1,3]. Here's the deal, I want the client to be updated by removing the 2, but not the [4,5,6] that do not correspond to the business_type in question so that it ends up [1,3,4,5,6], I'm using an after_update callback to update the clients' elements but the _was method doesn't work for HABTM relationships (to get the old business_type's elements.
I've tried using a before_update callback to first to client.elements = client.elements - business_type.elements to store momentarily in the DB [1,2,3,4,5,6] - [1,2,3] = [4,5,6], and in the after_update do client.elements = client.elements + business_type.elements to get [4,5,6] + [1,3] = [1,3,4,5,6]but this has already the new value of [1,3]. How can I get the old business_type.elements value in the before_update or after_update?
Thanks in advance for your help!
I had a similar problem in an app, and the only solution I could come up with was to store the values before doing update_attributes in the controller.
Example code:
Models
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories, :join_table => "categories_products"
def remember_prev_values(values)
#prev_values = values
end
def before_update_do_something
puts #prev_values - self.category_ids # Any categories removed?
puts self.category_ids - #prev_values # Any categories added?
end
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :products, :join_table => "categories_products"
end
In the update method in the products controller I do the following:
class ProductsController < ApplicationController
...
def update
#product.remember_prev_values(#product.category_ids)
if #product.update_attributes(params[:product])
flash[:notice] = "Product was successfully updated."
redirect_to(product_path(#product))
else
render :action => "edit"
end
end
...
end
It is not ideal, but it is then possible to "catch" the habtm inserts/removes before they are executed.
I do think it is possible to do in a callback, but you might need to "hack" into ActiveRecord.
I did not spend much time on trying to dig into ActiveRecord internals, as this is a simple implementation that works.
You should use after_initialize callback to store previous values.
after_initialize do #previous_elements = elements.map{|x| x} end
Note that here we make a copy of assosiations by map function call.
I'm writing an app where I need to compare two :datetime fields and get the difference to display as an "elapsed" time from the start of the record. I think I have my code right, but I keep getting the below error. Can someone tell me what I'm doing wrong?
Error on view:
undefined method `-' for nil:NilClass line 26:
26: <td><%= link_to call.elapsed_time, call %></td>
Call.rb (abbreviated)
before_create :set_dispatched_time
def set_dispatched_time
self.dispatched_time = Time.now
end
def elapsed_time
self.dispatched_time - Time.now
end
My fields in PG are set as :datetime so that I can compute times (I did have them as strings.. ooops) but for some reason it's not calculating. Do I need to call Time.parse first or something like that? I'm not really sure which direction to go. I just want to subtract the dispatched_time field from Time.now
Your self.dispatched_time does not exist according the error message, it is nil. This can be because before_create is call when first time the opject is persisted into the database, and this particular instance is not yet saved.
First try to make sure, that this variable has a value, or assign a default value in case if it is not assigned. E.g.:
def elapsed_time
Time.now - (self.dispatched_time || Time.now)
end
Let's say I have a model called SchoolYear. This model has many periods. When I want create an evaluation structure, it automatically creates three periods with default values.
class SchoolYear < ActiveRecord::Base
has_many :periods, :dependent => :destroy
...
end
Later on, I have a view in which the user can update all of these 3 periods, along with the school year (the form is based on the school year). A period has a status field, which may be active, inactive or finished. When the school year is created, the default value for its period's status is inactive
class Period < ActiveRecord::Base
ACTIVE = 1
INACTIVE = 2
FINISHED = 3
belongs_to :school_year
validates_inclusion_of :status, :in => [ACTIVE, INACTIVE, FINISHED]
end
At least one of the periods for the school year needs to have an active status when updating the school year. How can I validate this? If I try to do this in the SchoolYear model, all its periods will still have the default values. In the Period model, only the current period (self) has its new values. When examining the other periods, they all have the default values. They do get updated, but what I mean is that when I try to validate what I want, they still present their old values because they still haven't been updated in the database, and Rails isn't looking for their values in memory.
For example, if I want to do this validation in the SchoolYear model, I may have something like this:
class SchoolYear < ActiveRecord::Base
validate :at_least_one_active_period, :on => :update
def at_least_one_active_period
self.errors.add(:periods_error, "Period Error") unless self.periods.where(:status => Period::ACTIVE).count == 1
end
end
end
This doesn't work out because, as I said, Rails retrieves these values directly from the database, and doesn't take into account what has been passed by the form. Can you help me? Thanks!
I am programming a booking system.
I want my users to be able to book only one (or a defined number of) resources at a time. However, I do not want to remove "past" reservation for my database, since it will be used for invoicing purposes. On reservation creation, I need to validate that a user has not exceeded its reservation quota, which means has not more than "quota" reservations in the future.
class User < ActiveRecord::Base
has_many :reservations
def active_reservations
#maybe worth to rewrite with a "find"?
my_list = []
reservations.each do |reservation|
if (not reservation.past?)
my_list.push(reservation)
end
return my_list
end
class Reservation < ActiveRecord::Base
belongs_to :user
validate :respect_user_quota
def past?
return (date < Date.now)
def respect_user_quota
if (user.active_reservations.count > user.quota)
errors.add(:user, "User quota exceeded!")
Is this the right way to implement this validation? What could be wrong there (I never see the error message). Should the quota validation be moved to the user class?
I would try and do this more simply and move the validation to user.
class User < ActiveRecord::Base
has_many :reservations
validate :reservation_quota
if sum(reservations.active) > quota # User.quota is implied here
errors.add(:user, "User quota exceeded!")
end
class Reservation < ActiveRecord::Base
belongs_to :user
def active
active? 1 : 0
# If there's a boolean 'active' flag the ? method gets created automatically.
# This could be (reservation_date < Date.now)? ? 1 : 0 for you.
# Using `(expression)? ? true : false` is using the Ternary operator.
end
end
I'd like to have a maximum number of associated records on a model.
E.g. a project has_many tasks, but not more then twenty.
How can I enforce this rule?
The only solution that I've been able to come up with so far is an
INSERT INTO...SELECT query like this:
INSERT INTO
tasks (`id`,`project_id`,`title`,`body`)
SELECT
NULL, ?, ?, ?
FROM
tasks
HAVING
count(id) < MAX_NUMBER_OF_TASKS
LIMIT 1;
As far as I can tell, this will guarantee a maximum number of tasks being inserted. Am I correct in this?
Is there a 'Rails way' to do this?
Is it possible to override ActiveRecord/the Task model so that it uses the query above
to insert a new record?
I'm currently using a custom method with ActiveRecord::Base.connection
and calling that instead of .create or .save when new_record? == true.
I haven't been able to try this, but I can't see why it shouldn't work.
Step one: Define a validator on the parent object (this is a simple implementation - could/should be made more generic):
class Project < ActiveRecord::Base
validate :max_tasks
def max_tasks
if tasks.count > 20
errors.add_to_base("Should not have more than 20 tasks")
end
end
end
Step two: Turn on validation of project from tasks:
class Task < ActiveRecord::Base
validates_associated :project
end
And I think you should be in business. When you try and save a new task, it'll validate the associated project, and the validation will fail if there are (now) more than 20 tasks associated.
Just in case you fancy making this more generic, you could do something like:
class NumberOfAssociatedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if options[:maximum] && record.send(attribute).count > options[:maximum]
record.errors[attribute] << "must not have more than #{options[:maximum]}"
end
if options[:minimum] && record.send(attribute).count < options[:minimum]
record.errors[attribute] << "must not have less than #{options[:minimum]}"
end
end
end
class MyModel < ActiveRecord::Base
validates :my_association, :number_of_associated => {:maxiumum => 20}
end
May be you can add some pre save validation to your model, that checks how many associated models it already have, and throw a validation error if it exceeds your max number of associations.