Rails Overalps DateTime - ruby-on-rails

I'm currently trying to check if two datetime ranges overlap. I'm having two problems
First, the overlaps ALWAYS returns false, even when I commit the same start time over and over with a duration of 1 hour (i.e. the start time is 3PM and end time is 4PM, and repeated commits always create a new session without returning true statement via overlap and returning a JSON saying the Trainer is already booked). So the if statement is never hit.
Seconds, Overlaps in rails uses >= in its method, and I'm unable to overwrite this to include > (endpoints don't return true i.e. 3-4PM and 4-5PM shouldn't overlap).
How can I check two datetimes and return true without including endpoints? Am i formatting the dates wrong from JSON? Is my if statement wrong? They are stored in postgresql as datetime with timezones (timestampz).
def create_request_iphone
#start_time = DateTime.parse(params[:start_time])
#end_time = #start_time + params[:duration].to_f.hour
#logic to check if trainer is booked at that time
#trainer = Trainer.where(id: params[:trainers_id]).first
#booked_session = #trainer.session_details
//logic to check if trainer has booked sessions that overlap
#booked_session = #trainer.session_details
#booked_session.each do |aSession|
if (aSession.start_time..aSession.end_time).overlaps?(#start_time..#end_time)
respond_to do |format|
msg = {:status => "FAILURE", :message => "TRAINER ALREADY BOOKED"}
format.json { render :json => msg }
end
return
end
end
#session_detail = SessionDetail.new(trainers_id: #trainer.id)
unless(!#session_detail.save)
respond_to do |format|
msg = {:status => "SUCCESS", :message => #session_detail.as_json)
format.json { render :json => msg } # don't do msg.to_json
end
end
end

Use the triple-dot ... version of Range, which excludes the right end-point.
For example:
(1..4).overlaps? (4..8)
# => true
(1...4).overlaps? (4..8)
# => false

In addition to the triple ... suggestion by #matt , I found the following which also works
def overlap?(x,y)
(x.first - y.end) * (y.first - x.end) > 0
end
The key was to drop the >= that was included in active record.
http://makandracards.com/makandra/984-test-if-two-date-ranges-overlap-in-ruby-or-rails

Related

In my database I have time stored in minutes. The user edits it in 24hr format on a form. How should I code it? Rails 4

I have an integer field in a database that represents minutes from midnight for a single day. On the form that displays the time value, I want to show a value between 00:00 and 23:59. I'm using a text_field.
When the form is submitted, I want the following events to occur in this order.
1) Validate the form string using a regular expression to make sure it is between 00.00 and 23.59.
2) If validation passes, modify the string to minutes from midnight.
3) Save the information to database.
Here's how I implemented it.My model is called availabilities. and the field is called start_tod, (start time of day)
class Availability < ActiveRecord::Base
validates_format_of :start_tod, with: /\A([01][0-9]|2[0-3])(:|\.)([0-5][0-9])\Z/
end
and in the availabilities controller I have. str_to_tod() simply converts the time string to an integer.
def update
params[:availability][:start_tod] = str_to_tod(params[:availability][:start_tod])
respond_to do |format|
if #availability.update(availability_params)
format.html { redirect_to session[:previous_url], notice: 'Availability was successfully updated.' }
format.json { render :show, status: :ok, location: #availability }
else
format.html { render :edit }
format.json { render json: #availability.errors, status: :unprocessable_entity }
end
end
end
Similar process is applied to the create method. If I try to enter a start time of day on the form of 09:30, validation fails. I've printed the value out and discovered when I validate it, it has already been converted to 540 from 09:30. How do I make the validation operate on the 09:30 string. If there's a better way of doing this please let me know.
you will need "before_type_cast"
So you have to write your own validation method like this:
validate(:format_to_time)
def format_to_time
return /\A([01][0-9]|2[0-3])[:.]([0-5][0-9])\Z/.match(self.start_tod_before_type_cast)
end
Ok. I'm going to answer my question. I think there is a better solution. Use a java time picker or HTML5 time or a regular expression. It's a much neater solution. Here's how I did it...
On the form I have
<%= f.text_field :start_tod, value: tod_to_str(#availability.start_tod), pattern: '([01][0-9]|2[0-3])[:|\.][0-5][0-9]|24[:|\.]00', title: '00:00 to 24:00' %>
where #availability.start_tod is the time in minutes from midnight and tod_to_str() is the function that converts minutes to a string like 14:00. the pattern ensures that only a valid time string will ever make it's way back to the server.
In the model I have
before_validation :type_cast_times
def str_to_tod(tod_str)
return tod_str[0].to_i * 600 +
tod_str[1].to_i * 60 +
tod_str[3].to_i * 10 +
tod_str[4].to_i * 1
end
def type_cast_times
self.start_tod = str_to_tod(self.start_tod_before_type_cast)
end
You have to call type_cast_times() before validation, so if any server validation fails, the correct time value in minutes is passed back to the form when it displays the error

Rails - Updating Boolean Attribute in a model on Create

I'm creating an app that lets users purchase items from an online store. I followed the RailsCasts episodes, and built my OrdersController like so.
def create
#order = current_cart.build_order(order_params)
#order.ip_address = request.remote_ip
if #order.save
if #order.purchase
Item.where(email: Order.last.email).last.purchased == true
PurchaseMailer.confirmation_email(Item.last.email).deliver
flash[:notice] = "Thanks for your purchase"
redirect_to root_path
else
flash[:danger] = "Something was wrong"
redirect_to :back
end
else
render :action => 'new'
end
end
I recently decided to add an attribute to my items, which says whether or not they've been purchased or not. Items in the cart have not yet been purchased. I created a migration, giving all items a purchased attribute, that is a boolean.
By default, items are not purchased, so the default value is false.
class AddPurchasedToItem < ActiveRecord::Migration
def change
add_column :items, :purchased, :boolean, :default => false
end
end
That's why I added this line of code to my Orders#Create action.
Item.where(email: Order.last.email).last.purchased == true
Here I was setting the value of purchased from false to true. However, when I load up rails console
Item.last.purchased
=> false
It looks like the value still isn't being stored
As another response points out, you're using the == to assign a value, which isn't right. You need = instead.
And you have to save an item after you assign a value to it.
An example:
conditions = {email: Order.last.email} # using your conditions
item = Item.find_by(conditions)
item.purchased = true
item.save # this is what you're missing
Item.find(item.id).purchased # will be true
Another way to update is the following:
item.update_attribute(:purchased, true)
Yet another way is to call update_all on the ActiveRecord::Relation object like so:
# update all items that match conditions:
Item.where(conditions).update_all(purchased: true)
Which method you choose may depend on the scenario as update_all doesn't run the callbacks you specify in the model.
In your case however, all you're missing is the item.save line.
Item.where(email: Order.last.email).last.purchased == true
You're using a == operator to try to assign a value. Try using = instead.

Ruby on Rails - Undefined methods for NilClass

I'm creating a picture-rating app where users can click on pictures and rate them on a scale from 1 to 5. I'm trying to calculate the average rating of a picture. Before when users clicked on a rating value, that value became the picture's rating.
Rating: 5
If a user clicked on 1, the rating would change to 1
Rating: 1
When reality, the rating should have been 3.
(5 + 1) / 2
=> 3
Here's what I've accomplished so far in implementing this feature.
I added a migration to create two new columns for my Pictures Table
rails g migration AddRatingsToPictures ratings_count: integer, rating_total: integer
Both the new attributes, ratings_count and rating_total are integer types, meaning they are assigned a nil value at default.
p = Picture.first
p.attribute_names
=> ['id', 'title', 'category', 'stars', 'updated_at', 'created_at',
'ratings_count', 'rating_total']
p.ratings_count
=> nil
p.rating_total
=> nil
My only problem is the NilClass Error.
Here is my update method in my PicturesController.
def update
#picture = Picture.find(params[:id])
#picture.ratings_count = 0 if #picture.stars.nil?
#picture.rating_total = #picture.stars
#picture.rating_total += #picture.stars if #picture.stars_changed?
#picture.ratings_count += 1 if #picture.rating_total_changed?
if #picture.update_attributes(picture_params)
unless current_user.pictures.include?(#picture)
#picture = Picture.find(params[:id])
current_user.pictures << #picture
redirect_to #picture, :flash => { :success => "Thank you! This picture has been added to your Favorites List" }
else
redirect_to :action => 'index'
flash[:success] = 'Thank you! This picture has been updated'
end
else
render 'edit'
end
end
Here is my picture_param method in my PicturesController
def picture_params
params.require(:picture).permit(:title, :category, :genre, :stars)
end
Here is what the two new columns do
ratings_count: Calculates the number of times a picture has been rated
rating_total: Calculates the sum of the stars a picture has received
In the above code, I first set the ratings_count to 0 if the picture doesn't have a rating. This means that the picture hasn't been rated yet.
I then need to initially set the rating_total to the number of stars a picture has. If a user changed the star rating, I would add those stars to the rating_total. And if the total increased, that's my cue to increase the number of ratings.
Obviously, to calculate the average, I'd do something like this.
(#picture.rating_total / #picture.ratings_count).to_f
Now, I think I have the right idea but I know why this doesn't work. When columns are created with an integer value, by default they are set to nil. This leads to a NilClass Error when I load the web page.
undefined method `/' for nil:NilClass
Here is my code in the View
<li><strong>Rating:</strong> <%= pluralize((#picture.rating_total / #picture.ratings_count), 'Star') %></li>
Ok, the main reason it is not working is because
you fetch the picture
you check the stars from the database, and the NOT the passed form-parameters
you do update_attributes, which if I am not mistaken, used to set attributes and then save the complete object, but since rails 4 only updates the passed attributes (which is what you would expect)
One small remark: keeping the rating correct is a function I would place in the model, NOT in the controller.
Furthermore, how to handle the if nil, initialise to zero I wrote a short blogpost about. In short: overrule the getter.
So I would propose the following solution. In your model write
class Picture < ActiveRecord::Base
def ratings_count
self[:ratings_count] || 0
end
def ratings_total
self[:ratings_total] || 0
end
def add_rating(rating)
return if rating.nil? || rating == 0
self.ratings_count += 1
self.ratings_total += rating
self.stars = self.ratings_total.to_f / self.ratings_count
self.save
end
def rating
return 0 if self.ratings_count == 0
self.ratings_total.to_f / self.ratings_count
end
and then the code in your controller becomes much cleaner and readable:
def update
#picture = Picture.find(params[:id])
stars = picture_params.delete(:stars)
if #picture.update_attributes(picture_params)
#picture.add_rating stars
unless current_user.pictures.include?(#picture)
current_user.pictures << #picture
redirect_to #picture, :flash => { :success => "Thank you! This picture has been added to your Favorites List" }
else
redirect_to :action => 'index'
flash[:success] = 'Thank you! This picture has been updated'
end
else
render 'edit'
end
end
I first delete the :stars from the parameters, because I do not want to save those, I want to use those for the add_rating. I then try to update_attributes, which will fail if there are any failing validations, and if that is ok, I will add_rating which itself will handle nil or zero correctly. Well granted: I do not know how you handle a "non-rating" (nil? zero?). It is possible a rating of zero should be added, because it will add a rating, but most UI I know do not allow to select 0 as rating, so you might want to change the zero handling.
This will handle the case of uninitialized (nil) values in your attributes...
def update
#picture = Picture.find(params[:id])
if #picture.stars_changed?
#picture.ratings_count = (#picture.ratings_count || 0) + 1
#picture.rating_total = (#picture.rating_total || 0) + ( #picture.stars || 0)
end
You don't need an array of ratings or ratings persisted to database, assuming you only count votes where the rating changes, you can accumulate the count and the total and divide the two (which is, in fact, what you're doing so I'm preaching to the converted).
Although it seems to me that if I change a picture from 5 to 1 and it only changes to 3, I'm gonna keep clicking 1 :)
You could set the default value on the migration when you created it. But no worries, you can create a new migration to change it:
# Console
rails g migration change_default_for_ratings_count_and_rating_total
# Migration Code
class ChangeDefaultForRatingsCountAndRatingTotal < ActiveRecord::Migration
def change
change_column :pictures, :ratings_count, :integer, default: 0
change_column :pictures, :rating_total, :integer, default: 0
end
end
Keep in mind that some databases don't automatically assign newly updated default values to existing column entries, so maybe you will have to iterate over every picture already created with nil values and set to 0.
Ok, an alternative...
Do an after_initialize so the fields are never, never, ever nil. Even if you're creating a new Picture object, they'll be initialized as zero. Problem will go away.
class Picture << ActiveRecord::Base
after_initialize do |picture|
picture.ratings_count ||= 0
picture.rating_total ||= 0
end
...
end

How to define time intervals in Rails?

There is the following task: I need to store in db (PostgreSQL) some time intervals; for example, 1 PM - 2 PM in order to I can test if some time exists in some interval. How should I do it properly? May be there is some gem for it? Thanks for it.
You can save the columns as #Santosh said and then if you need to check something against a time range you can use this useful class:
Time-of-day range in Ruby?
class TimeRange
private
def coerce(time)
time.is_a? String and return time
return time.strftime("%H:%M")
end
public
def initialize(start,finish)
#start = coerce(start)
#finish = coerce(finish)
end
def include?(time)
time = coerce(time)
#start < #finish and return (#start..#finish).include?(time)
return !(#finish..#start).include?(time)
end
end
which can be used this way:
irb(main):013:0> TimeRange.new("02:00","01:00").include?(Time.mktime(2010,04,01,02,30))
=> true
irb(main):014:0> TimeRange.new("02:00","01:00").include?(Time.mktime(2010,04,01,01,30))
=> false
irb(main):015:0> TimeRange.new("01:00","02:00").include?(Time.mktime(2010,04,01,01,30))
=> true
irb(main):016:0> TimeRange.new("01:00","02:00").include?(Time.mktime(2010,04,01,02,30))
=> false

What's the right way to modify the params passed to controller in rails 3.1.0?

In our Rails 3.1.0 app, we need to modify params passed to rfq controller in create and update. For example, we want to record the current user id under input_by_id. What we did was:
#rfq.input_by_id = session[:user_id]
It worked as expected. Also when need_report field is false, then report_language field should be nil. We decide to add the following line in rfq controller to make sure the nil is passed to report_language when need_report is false:
#rfq.report_language = nil unless params[:need_report]
However this addition causes the rspec case failure (in create/update of the controller) because of the data validation failure. However when we fire up the app, it behaves fine without saving the report_language when need_report is false. I am wondering if the line above is not the right way to use params[:need_report] for #rfq updating.
Thanks so much.
UPDATE:
Controller code:
def create
if has_create_right?
#rfq = Rfq.new(params[:rfq], :as => :roles_new )
#rfq.input_by_id = session[:user_id]
#save sales_id selected
if sales? && member? && !team_lead?
#rfq.sales_id = session[:user_id]
end
#view page may carry the hidden report language even if need_report == false
#rfq.report_language = nil unless params[:need_report]
#save into join table rfqs_standards
params[:rfq][:standard_ids].each do |sid|
#rfq.standards << Standard.find(sid.to_i) if !sid.nil? && sid.to_i > 0
end unless params[:rfq][:standard_ids].nil?
#save into join table rfqs_test_items
params[:rfq][:test_item_ids].each do |tid|
#rfq.test_items << TestItem.find(tid.to_i) if !tid.nil? && tid.to_i > 0
end unless params[:rfq][:test_item_ids].nil?
if #rfq.save!
redirect_to URI.escape("/view_handler?index=0&msg=RFQ saved!")
else
flash.now[:error] = "RFQ not saved!"
render 'new'
end
else
redirect_to URI.escape("/view_handler?index=0&msg=No rights!")
end
end
Test case failed after addition of #rfq.report_language = nil unless params[:need_report]
it "should be successful for corp head" do
session[:corp_head] = true
session[:user_id] = 1
s = Factory(:standard)
rfq = Factory.attributes_for(:rfq, :need_report => true, :report_language => 'EN')
rfq[:standard_ids] = [s.id] # attach standard_id's to mimic the POST'ed form data
get 'create', :rfq => rfq
#response.should redirect_to URI.escape("/view_handler?index=0&msg=RFQ saved!")
response.should render_template('new')
end
the problem ist that you are simply not looking at the right value.
get 'create', :rfq => rfq will result in a params-hash like {:rfq => {...}}
so you need to #rfq.report_language = nil unless params[:rfq][:need_report] == 'true'

Resources