Setting Time Zone Per Record - ruby-on-rails

Any idea how I can use the time zone generated from a location to create a new record with that respective zipcode?
I have created a service object to help pull the zipcode information.
I am able to use this info in the terminal to set the zip code but it doesn't work when I try a before_save or before_create hook.
class ServiceObject
include ActiveModel::Model
def self.get_timezone_name(location)
z = GoogleTimeZone.fetch(location.latitude, location.longitude)
ActiveSupport::TimeZone.find_tzinfo(z.time_zone_id)
end
end
class Location < ActiveRecord::Base
has_many :events
#name - String
#start_time - DateTime
#end_time - DateTime
end
class Event < ActiveRecord::Base
belongs_to :location
geocoded_by :full_address
before_create :zoned
private
def zoned
Time.zone = ServiceObject.get_time_zone(self.location)
end
end
I also tried to use the date time attribute gem to set an event's time zone. Again this works in the console but not with a call back. Records are not created via a browser but rather in the console.

Here is a blog post I wrote on timezones with rails: http://jessehouse.com/blog/2013/11/15/working-with-timezones-and-ruby-on-rails/
There are two different ways to accomplish what you want:
Time.use_zone block when saving data
use in_time_zone when displaying data
I recommend you save the timezone on your location, update it if the long/lat changes; it looks like your Event and Location example above is flipped? Event should have a start and end, not location?
class Location
has_many :events
geocoded_by :full_address
before_save :set_time_zone
private
def set_time_zone
if new_record? || latitude_changed? || longitude_changed?
self.time_zone = ServiceObject.get_time_zone(self)
end
end
end
class Event
belongs_to :location
end
then in the console or controller code
location = Location.last
Time.use_zone(location.time_zone) do
location.events << Event.new({ ... })
end

Related

How to check if associated model has entries in Rails 5?

I have a model RegularOpeningHour(dayOfWeek: integer) that is associated to a model OpeningTime(opens: time, closes: time). RegularOpeningHour has an 1:n relation to OpeningTime, so that a specific day can have many opening times.
(I know that I simply could have one entry with 'opens' and 'closes' included in RegularOpeningHour but for other reasons I need this splitting)
Now I want a open?-Method, that returns whether the business is opened or not. I tried the following in my model file regular_opening_hour.rb:
def open?
RegularOpeningHour.where(dayOfWeek: Time.zone.now.wday).any? { |opening_hour| opening_hour.opening_times.where('? BETWEEN opens AND closes', Time.zone.now).any? }
end
Unforutnately, that doesn't work. Any ideas to solve this?
How about this:
def open?
joins(:opening_times)
.where(dayOfWeek: Time.current.wday)
.where("opens <= :time AND closes >= :time", time: Time.current)
.any?
end
EDIT: Missing ':' in the join
You could create some scopes to make selecting open OpeningTimes and open RegularOpeningHours less clunky. This makes creating the given selection much easier.
class OpeningTime < ApplicationRecord
# ...
belongs_to :regular_opening_hour
def self.open
time = Time.current
where(arel_table[:opens].lteq(time).and(arel_table[:closes].gteq(time)))
end
# ...
end
class RegularOpeningHour < ApplicationRecord
# ...
has_many :opening_times
def self.open
where(
dayOfWeek: Time.current.wday,
id: OpeningTime.select(:regular_opening_hour_id).open,
)
end
# ...
end
def open?
RegularOpeningHour.open.any?
end
Since you have has_many association of RegularOpeningHour to OpeningTime you can use join query like below.:
RegularOpeningHour.joins(:opening_times).where(dayOfWeek: Time.zone.now.wday).where('? BETWEEN opening_times.opens AND opening_times.closes', Time.zone.now).any?

Update another column if specific column exist on updation

I have a model which have two columns admin_approved and approval_date. Admin update admin_approved by using activeadmin. I want when admin update this column approval_date also update by current_time.
I cant understand how I do this.Which call_back I use.
#app/models/model.rb
class Model < ActiveRecord::Base
before_update 'self.approval_date = Time.now', if: "admin_approved?"
end
This assumes you have admin_approved (bool) and approval_date (datetime) in your table.
The way it works is to use a string to evaluate whether the admin_approved attribute is "true" before update. If it is, it sets the approval_date to the current time.
Use after_save callback inside your model.
It would be something like this:
after_save do
if admin_approved_changed?
self.approval_date = Time.now
save!
end
end
Or change the condition as you like!
You could set the approval_date before your model instance will be saved. So you save a database write process instead of usage of after_save where you save your instance and in the after_save callback you would save it again.
class MyModel < ActiveRecord::Base
before_save :set_approval_date
# ... your model code ...
private
def set_approval_date
if admin_approved_changed?
self.approval_date = Time.now
end
end
end
May be in your controller:
my_instance = MyModel.find(params[:id])
my_instance.admin_approved = true
my_instance.save

Displaying timestamps for boolean values in Rails 4

So I am writing a feature in my Rails app that tracks packages. The way I currently have it set up, each package has many locations (let's say: location 1, location 2, location 3...) which are all initially false. When a package has passed through a certain location, the managers are supposed to mark that as true until it reached the final location. But in the show page for a package I want to show not only these true/false values, but also the exact time when a package passed through each individual location (when it goes from false to true). Any ideas how I might implement/display that?
Create a marked_at accessor on your class (or create a migration to add the marked_at field to the database):
class Package
attr_accessor :marked_at
def marked?
self.marked_at.present?
end
end
When a package is marked you can just set package.marked_at = Time.now and use package.marked? to check if it has been marked.
You can also add
def mark!
self.marked_at = Time.now
end
to your class to mark it with package.mark! from outside. You can also do some logic in the mark! method, like passing in a location and checking if the location is the one where you want to mark your package:
def mark!(location)
# check if we're at the correct location
self.marked_at = Time.now if location = self.correct_location
end
Try associations instead of field.
class Package < ActiveRecord::Base
has_many :locations
end
class Location < ActiveRecord::Base
belongs_to :package
end
Then, give your Location the boolean attribute present and the string attribute name. When a package arrives at location 1, create a new location and set that location to name: "location", present: true. Flip the value when the package arrives at location2. Use location.created_at as a time stamp, or do the following:
class Location < ActiveRecord::Base
belongs_to :package
after_create :mark_time
after_save :mark_other_location_false
def mark_time
self.time_stamp = Time.now
end
def mark_other_location_false
locations_list = self.package.locations
other_locations = locations_list - self
other_locations.map{ |l| l.present = false }
end
end
This will set your timestamp and mark other locations false.

rails 4 attr_accessor and new records

I have a model Booking with attr_acessor :date and time:
class Booking < ActiveRecord::Base
# ...
before_save :convert_date_and_time
attr_accessor :date, :time
def convert_date_and_time
self.date_and_time = DateTime.parse("#{date.to_s} #{time.to_s}")
end
end
I am trying to define getter methods for date and time:
def date
date_and_time.to_date if self.id.present?
end
def time
date_and_time.to_time if self.id.present?
end
but I think this is not quite the way to do it. I need self.id.present? because when I am trying to create a new record, obviously date_and_time still has no value and the getters will yield errors.
If that is the case, how should the getters look like so that I can handle new records that are not yet saved? Should I leave them like how they are now?
Thanks!
To detect new record you can use new_record?, but in your case you can use try :
date_and_time.try(:to_date)
date_and_time.try(:to_time)

Rails replace collection instead of adding to it from a has_many nested attributes form

I have these models (simplified for readability):
class Place < ActiveRecord::Base
has_many :business_hours, dependent: :destroy
accepts_nested_attributes_for :business_hours
end
class BusinessHour < ActiveRecord::Base
belongs_to :place
end
And this controller:
class Admin::PlacesController < Admin::BaseController
def update
#place = Place.find(params[:id])
if #place.update_attributes(place_params)
# Redirect to OK page
else
# Show errors
end
end
private
def place_params
params.require(:place)
.permit(
business_hours_attributes: [:day_of_week, :opening_time, :closing_time]
)
end
end
I have a somewhat dynamic form which is rendered through javascript where the user can add new opening hours. When submitting these opening hours I would like to always replace the old ones (if they exist). Currently if I send the values via params (e.g.):
place[business_hours][0][day_of_week]: 1
place[business_hours][0][opening_time]: 10:00 am
place[business_hours][0][closing_time]: 5:00 pm
place[business_hours][1][day_of_week]: 2
place[business_hours][1][opening_time]: 10:00 am
place[business_hours][1][closing_time]: 5:00 pm
... and so forth
These new business hours get added to the existing ones. Is there a way to tell rails to always replace the business hours or do I manually have to empty the collection in the controller every time?
Bit optimizing the solution proposed #robertokl, to reduce the number of database queries:
def business_hours_attributes=(*args)
self.business_hours.clear
super(*args)
end
This is the best I could get:
def business_hours_attributes=(*attrs)
self.business_hours = []
super(*attrs)
end
Hopefully is not too late.
You miss id of business_hours try:
def place_params
params.require(:place)
.permit(
business_hours_attributes: [:id, :day_of_week, :opening_time, :closing_time]
)
end
That's why the form is adding a new record instead of updating it.

Resources