I have 3 classes defined as:
Client
class Client < ActiveRecord::Base
belongs_to :provider
has_many :meeting_times
end
Provider
class Provider < ActiveRecord::Base
has_many :clients
has_many :meeting_times
end
MeetingTime
class MeetingTime < ActiveRecord::Base
belongs_to :meetingable, polymorphic: true
end
Each instance of MeetingTime has a day, start_time and end_time.
Is there a way for me to efficiently compute the intersection of
client_instance1.meeting_times and provider_instance1.meeting_times
In my seed file, I create meeting_times for clients and providers as follows
client_instance1.meeting_times.build(:day => "Friday", :start_time => time1, :end_time => time2)
client_instance1.meeting_times.build(:day => "Saturday", :start_time => time3, :end_time => time4)
client_instance1.save!
Currently when I try
client_instance1.meeting_times & provider_instance1.meeting_times
after developing the client and provider instances in a seed file, I get an empty set because the ids of different meeting times don't match (even if the day, start_time and end_time are the same).
How could I resolve this issue? Am I misunderstanding something?
I believe you can do:
intersections = client_instance1.meeting_times.reduce([]) do |result, client_meeting|
intersects = provider_instance1.meeting_times.any? { |provider_meeting| provider_meeting.day == client_meeting.day && provider_meeting.start_time == client_meeting.start_time && provider_meeting.end_time == client_meeting.end_time }
result << obj if intersects
result
end
But following this approach, keep in mind, it'll return only client_instance1 meetings, which have equivalent date/time in provider_instance1 meetings.
The smarter approach - is to build a hash, where the keys will be a checksum of string like: "#{day}#{start_time}#{end_time}".
And values - are arrays of equal meetings, both from client and providers meetings.
Related
In my Rails application I have a User model ,Department model, Group model and a Register model.User model has basic user information,
User Model:
id , name
has_and_belongs_to_many :departments , :groups
Department Model:
id,name
has_and_belongs_to_many :users
has_many :registers
Group Model:
id,name
has_and_belongs_to_many :users
has_many :registers
Register Model:
date ,department_id , group_id , one , two , three
belongs_to :department ,:group
Among the Register Model "one" , "two" , "three" are time slots say: 9-12 , 12-3 , 3-6.Every day each user has to mark attendance so that their attendance has to mark against the time slot in Register table. How to mark attendance based on current time with the time slot.
Thanks in advance.
You may need to do
current_time = Time.now
if (9..11).include?(current_time.hour)
# Do A
elsif (12..14).include?(current_time.hour)
# Do B
elsif (15..18).include?(current_time.hour)
# Do C
end
(or)
current_time = Time.now
if (9...12).include?(current_time.hour)
# Do A
elsif (12...15).include?(current_time.hour)
# Do B
elsif (15...18).include?(current_time.hour)
# Do C
end
# I think of the extra "." as 'eating' the last value so you don't have it.
to deal with the overlaps
try this
current_time = Time.now
if (9..12).include?(current_time.hour)
# 9-12
elsif (12..15).include?(current_time.hour)
# 12-3
elsif (15..18).include?(current_time.hour)
# 3-6
end
Might consider creating a bitmap. A 32 bit integer is lightweight and gives you 0-24, one for each hour. Write some functions that can compare, set,... hour ranges.
There is something i don't quite understand in Rails's belongs_to concept. Documentation states:
Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object
Let's say i have an Employee entity:
class Employee < ActiveRecord::Base
belongs_to :department
belongs_to :city
belongs_to :pay_grade
end
Will the following code fire three updates and if so is there a better way to do it? :
e = Employee.create("John Smith")
Department.find(1) << e
City.find(42) << e
Pay_Grade.find("P-78") << e
You can simply assign it:
e = Employee.new(:name => "John Smith")
e.department = Department.find(1)
e.city = City.find(42)
e.pay_grade = Pay_Grade.where(:name => "P-78")
e.save
I changed the create to new to construct the object before saving it. The constructor takes a hash, not different values. find takes only the id and not a string, use where on a field instead.
You can also use the following:
Employee.create(:name => "John Smith",
:department => Department.find(1),
:city => City.find(42),
:pay_grade => PayGrade.where(:name => "P-78").first
Also note that model names should be camel case: PayGrade instead of Pay_Grade.
have model
Class ModelA < ActiveRecord::Base
has_many :apples
before_save :include_prime_apple_in_apples
has_one :prime_apple
def include_prime_apple_in_apples
self.apple_ids << prime_apple_1.id
end
end
l=ModelA.new(:apple_ids => [ "ap_1_id", "ap_2_id"],:prime_apple => prime_apple_1)
l.save
l.apple_ids.should include(prime_apple_1.id) # this doesnt seem to work
How change the params passed for associations?
There's something wrong there but to answer your question:
AFAIK you can only assign to "other_ids". Also you can push directly to the has_many relation:
self.apple_ids = self.apple_ids + [prime_apple_1.id]
or
self.apples << prime_apple_1
Do you have different foreign_key set in the Apple model?
has many will do a select * from apples where modela_id = X
has one will do a select * from apples where modela_id = X limit 1
Which means that whatever you'll set on the prime_apple accessor you'll get back the first Apple record ...
Let's say you're implementing rails app for a snowboard rental store.
A given snowboard can be in one of 3 states:
away for maintenance
available at store X
on loan to customer Y
The company needs to be able to view a rental history for
a particular snowboard
a particular customer
The rental history needs to include temporal data (e.g. Sally rented snowboard 0123 from Dec. 1, 2009 to Dec. 3 2009).
How would you design your model? Would you have a snowboard table with 4 columns (id, state, customer, store), and copy rows from this table, along with a timestamp, to a snowboard_history table every time the state changes?
Thanks!
(Note: I'm not actually trying to implement a rental store; this was just the simplest analogue I could think of.)
I would use a pair of plugins to get the job done. Which would use four models. Snowboard, Store, User and Audit.
acts_as_state_machine and acts_as_audited
AASM simplifies the state transitions. While auditing creates the history you want.
The code for Store and User is trivial and acts_as_audited will handle the audits model.
class Snowboard < ActiveRecord::Base
include AASM
belongs_to :store
aasm_initial_state :unread
acts_as_audited :only => :state
aasm_state :maintenance
aasm_state :available
aasm_state :rented
aasm_event :send_for_repairs do
transitions :to => :maintenance, :from => [:available]
end
aasm_event :return_from_repairs do
transitions :to => :available, :from => [:maintenance]
end
aasm_event :rent_to_customer do
transitions :to => :rented, :from => [:available]
end
aasm_event :returned_by_customer do
transitions :to => :available, :from => [:rented]
end
end
class User < ActiveRecord::Base
has_many :full_history, :class_name => 'Audit', :as => :user,
:conditions => {:auditable_type => "Snowboard"}
end
Assuming your customer is the current_user during the controller action when state changes that's all you need.
To get a snowboard history:
#snowboard.audits
To get a customer's rental history:
#customer.full_history
You might want to create a helper method to shape a customer's history into something more useful. Maybe something like his:
def rental_history
history = []
outstanding_rentals = {}
full_history.each do |item|
id = item.auditable_id
if rented_at = outstanding_rentals.keys.delete(id)
history << {
:snowboard_id => id,
:rental_start => rented_at,
:rental_end => item.created_at
}
else
outstanding_rentals[:id] = item.created_at
end
end
history << oustanding_rentals.collect{|key, value| {:snowboard_id => key,
:rental_start => value}
end
end
First I would generate separate models for Snowboard, Customer and Store.
script/generate model Snowboard name:string price:integer ...
script/generate model Customer name:string ...
script/generate model Store name:string ...
(rails automatically generates id and created_at, modified_at dates)
To preserve the history, I wouldn't copy rows/values from those tables, unless it is necessary (for example if you'd like to track the price customer rented it).
Instead, I would create SnowboardEvent model (you could call it SnowboardHistory if you like, but personally it feels strange to make new history) with the similiar properties you described:
ev_type (ie. 0 for RETURN, 1 for MAINTENANCE, 2 for RENT...)
snowboard_id (not null)
customer_id
store_id
For example,
script/generate model SnowboardEvent ev_type:integer snowboard_id:integer \
customer_id:integer store_id:integer
Then I'd set all the relations between SnowboardEvent, Snowboard, Customer and Store. Snowboard could have functions like current_state, current_store implemented as
class Snowboard < ActiveRecord::Base
has_many :snowboard_events
validates_presence_of :name
def initialize(store)
ev = SnowboardEvent.new(
{:ev_type => RETURN,
:store_id => store.id,
:snowboard_id = id,
:customer_id => nil})
ev.save
end
def current_state
ev = snowboard_events.last
ev.ev_type
end
def current_store
ev = snowboard_events.last
if ev.ev_type == RETURN
return ev.store_id
end
nil
end
def rent(customer)
last = snowboard_events.last
if last.ev_type == RETURN
ev = SnowboardEvent.new(
{:ev_type => RENT,
:snowboard_id => id,
:customer_id => customer.id
:store_id => nil })
ev.save
end
end
def return_to(store)
last = snowboard_events.last
if last.ev_type != RETURN
# Force customer to be same as last one
ev = SnowboardEvent.new(
{:ev_type => RETURN,
:snowboard_id => id,
:customer_id => last.customer.id
:store_id => store.id})
ev.save
end
end
end
And Customer would have same has_many :snowboard_events.
Checking the snowboard or customer history, would be just a matter of looping through the records with Snowboard.snowboard_events or Customer.snowboard_events. The "temporal data" would be the created_at property of those events. I don't think using Observer is necessary or related.
NOTE: the above code is not tested and by no means perfect, but just to get the idea :)
For the purposes of the discussion I cooked up a test with two tables:
:stones and :bowls (both created with just timestamps - trivial)
create_table :bowls_stones, :id => false do |t|
t.integer :bowl_id, :null => false
t.integer :stone_id, :null => false
end
The models are pretty self-explanatory, and basic, but here they are:
class Stone < ActiveRecord::Base
has_and_belongs_to_many :bowls
end
class Bowl < ActiveRecord::Base
has_and_belongs_to_many :stones
end
Now, the issue is: I want there to be many of the same stone in each bowl. And I want to be able to remove only one, leaving the other identical stones behind. This seems pretty basic, and I'm really hoping that I can both find a solution and not feel like too much of an idiot when I do.
Here's a test run:
#stone = Stone.new
#stone.save
#bowl = Bowl.new
#bowl.save
#test1 - .delete
5.times do
#bowl.stones << #stone
end
#bowl.stones.count
=> 5
#bowl.stones.delete(#stone)
#bowl.stones.count
=> 0
#removed them all!
#test2 - .delete_at
5.times do
#bowl.stones << #stone
end
#bowl.stones.count
=> 5
index = #bowl.stones.index(#stone)
#bowl.stones.delete_at(index)
#bowl.stones.count
=> 5
#not surprising, I guess... delete_at isn't part of habtm. Fails silently, though.
#bowl.stones.clear
#this is ridiculous, but... let's wipe it all out
5.times do
#bowl.stones << #stone
end
#bowl.stones.count
=> 5
ids = #bowl.stone_ids
index = ids.index(#stone.id)
ids.delete_at(index)
#bowl.stones.clear
ids.each do |id|
#bowl.stones << Stone.find(id)
end
#bowl.stones.count
=> 4
#Is this really the only way?
So... is blowing away the whole thing and reconstructing it from keys really the only way?
You should really be using a has_many :through relationship here. Otherwise, yes, the only way to accomplish your goal is to create a method to count the current number of a particular stone, delete them all, then add N - 1 stones back.
class Bowl << ActiveRecord::Base
has_and_belongs_to_many :stones
def remove_stone(stone, count = 1)
current_stones = self.stones.find(:all, :conditions => {:stone_id => stone.id})
self.stones.delete(stone)
(current_stones.size - count).times { self.stones << stone }
end
end
Remember that LIMIT clauses are not supported in DELETE statements so there really is no way to accomplish what you want in SQL without some sort of other identifier in your table.
(MySQL actually does support DELETE ... LIMIT 1 but AFAIK ActiveRecord won't do that for you. You'd need to execute raw SQL.)
Does the relationship have to be habtm?
You could have something like this ...
class Stone < ActiveRecord::Base
has_many :stone_placements
end
class StonePlacement < ActiveRecord::Base
belongs_to :bowl
belongs_to :stone
end
class Bowl < ActiveRecord::Base
has_many :stone_placements
has_many :stones, :through => :stone_placements
def contents
self.stone_placements.collect{|p| [p.stone] * p.count }.flatten
end
def contents= contents
contents.sort!{|a, b| a.id <=> b.id}
contents.uniq.each{|stone|
count = (contents.rindex(stone) - contents.index(stone)) + 1
if self.stones.include?(stone)
placement = self.stone_placements.find(:first, :conditions => ["stone_id = ?", stone])
if contents.include?(stone)
placement.count = count
placement.save!
else
placement.destroy!
end
else
self.stone_placements << StonePlacement.create(:stone => stone, :bowl => self, :count => count)
end
}
end
end
... assuming you have a count field on StonePlacement to increment and decrement.
How about
bowl.stones.slice!(0)