I have models below
class HospitalBlockingRemark < ApplicationRecord
belongs_to :hospital
belongs_to :worker
end
This is to keep hospital blocking a worker in chat conversation
class HospitalBlockingRemark < ApplicationRecord
belongs_to :hospital
belongs_to :worker
end
Every time hospital blocks a worker, they are required to give a remark in string. They can unblock and block again with different remark, therefore, a hospital with id 1 block a worker with id 3, can have remarks in multiple rows with id 1 and 2.
For example:
HospitalBlockingRemark
id
hospital_id
worker_id
remark
1
1
3
"remark reason 1"
2
1
3
"remark reason 2"
HospitalWorkerBlocking
id
hospital_id
worker_id
1
1
3
We wish to left join HospitalWorkerBlocking with HospitalBlockingRemark so that we can get an array of HospitalBlockingRemark instance on every HospitalWorkerBlocking instance
For example, hospital_worker_blockings[0].hospital_blocking_remarks will give us the list of remark instances, where hospital_worker_blockings[0] can have hospital_id of 1 and worker_id of 3, [1] can have hospital_id of 2 and worker_id of 4 (The whole table of HospitalWorkerBlocking records). How should we configure our model class or write a rails active record joins that able to support that?
Related
I have 3 tables.
product
product_attribute_mappings
product_attribute_values
Here are some rows of each table.
products
id |name
1058|shoes
product_attribute_mappings
id | product_id | product_attribute_id
438 | 1058 | 9
product_attribute_values
id | product_attribute_mapping_id | value
2001 | 438 | 18 oz
2002 | 438 | 19 oz
As you can see here,
product.id = product_attribute_mappings.product_id
product_attribute_values.product_attribute_mapping_id = product_attribute_mappings.id
I want to get all product attributes values like
product.product_attribute_values # ["18 oz", "19 oz"]
But I am not sure how I can make models with associations to get as I want.
Does anyone have any idea?
What you have is a really strange backwards variant of the Entity Attribute Value (EAV) pattern. It would make more sense if you had the normalized attribute definitions (eg volume, weight, number of doodads, etc) on one table and the entitiy, attribute and value on one table.
class Product < ApplicationRecord
has_many :product_attributes
has_many :product_attribute_types, through: :product_attributes
# eager loading scope
def self.eager_load_attributes
eager_load(product_attributes: :product_attribute_types)
end
end
# This is the normalization table that stores the definition of an attribute
# rails g model ProductAttribute name:string unit:string
class ProductAttributeType< ApplicationRecord
has_many :product_attributes
has_many :product_attribute_types, through: :product_attributes
end
# This is the actual table that defines the attributes
# rails g model ProductAttribute product:belongs_to product_attribute_type:belongs_to value:jsonb
class ProductAttribute < ApplicationRecord
belongs_to :product # the entity
belongs_to :product_attribute_type # the attribute
# just shortcuts
delegates :name, to: :product_attribute_type
delegates :unit, to: :product_attribute_type
end
This uses a JSON column as the value to alieviate one of the classical issues with EAV which is that you have to cast everything into a single (usually string) type column. JSON can store numbers (not terribly well), strings, arrays and objects.
This lets you iterate through the products and attributes with:
# eager loading avoids a n+1 query
#products = Product.eager_load_attributes.all
#products.each do |product|
product.product_attributes.each do |attr|
puts "#{attr.name}: #{attr.value}{attr.unit}"
end
end
You can change your association name from product_attribute_values to product_attribute_values_association, then define product_attribute_values as instance methods
I'm trying to use Active Record to find parents where none of the children meet a certain condition.
In my application, users can post Bids on Items. One bid is set as the winner via a status enum. I'm trying to return a list of the items that do not have a winning bid.
Here's the relevant code:
# item.rb
class Item < ApplicationRecord
has_many :bids
scope :without_winner, -> { joins(:bids).where.not(bids: { status: :won }) }
end
# bid.rb
class Bid < ApplicationRecord
belongs_to :item
enum status: { pending: 0, won: 1, lost: 2 }
end
My problem is that the current :without_winner scope will return an item every time it has a non-winning bid. For instance, if we have two Items with three Bids each:
Item 1
Bid 1 (won)
Bid 2 (lost)
Bid 3 (lost)
Item 2
Bid 4 (pending)
Bid 5 (pending)
Bid 6 (pending)
My current :without_winner scope would return Item 1 twice and Item 2 three times. My desired output would simply return Item 2 once, and not return Item 1 at all.
How can I correct my scope to return a list of unique items without a winning bid?
I haven't used a scope in a while although it's an awesome syntactic sugar. But If you're really stuck, you can try the class method below. It looks ugly, but the good thing is it fires only one query and filters according to your requirement.
class Item < ApplicationRecord
has_many :bids
def self.without_winner
includes(:bids).joins(:bids).distinct.reject do |item|
item.bids.pluck(:status).include? :won
end
end
end
You can add .distinct to your scope to weed out the duplicates:
scope :without_winner, -> { joins(:bids).where.not(bids: { status: :won }).distinct }
I got asked to build a website where users can make an appointment for a car testdrive.
The calendar is not limited to say, 10 days, so I cannot specify the dates beforehand. The car dealers should have the ability to block certain days or timeslots.
I came up with a Testdrive table looking something like this:
Testdrive
---------
- id
- user_id
- date
- timeslot
- client_title
- client_name
- client_firstname
- client_company
- client_street
- client_house_nr
- client_postal_code
- client_city
- client_email
- client_phone
- client_mobile
However, I'm now not sure how to model the "blocked" slots/dates thing. Was thinking of making another table like "TestdriveDate" or something, but then I'd be limiting the calendar to what's in that table... and I don't want the dealers to have to enable every day/timeslot, nor do I want to put that much data in my database. So, I guess I should have something like "BlockedDate", or "BlockedTimeSlot". In that case, however, I would have to check every date in the list on my frontend against this table.. which also doesn't feel right.
I guess the 'BlockedDate' approach would be the best way to go though? Looking for some help in modeling this so its useable for me as a developer, and for my users (the car dealers).
Do this:
#app/models/slot.rb
class Slot < ActiveRecord::Base
#columns id | day | time | created_at | updated_at
#This will be populated with all the available "slots" -- EG day 0, time 1
enum day: [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday]
enum time: [:0900, :1000, :1100, :1130, :1200, :1300, :1330, :1400]
has_many :test_drives
has_many :clients, through: :test_drives
end
#app/models/test_drive.rb
class TestDrive < ActiveRecord::Base
#columns id | client_id | slot_id | created_at | updated_at
belongs_to :client
belongs_to :slot
end
#app/models/client.rb
class Client < ActiveRecord::Base
#columns id | title | name | company | street | house_nr | postal_code | city | email | phone | mobile | created_at | updated_at
has_many :test_drives
has_many :slots, through: :test_drive
def firstname
name.split(" ").first
end
end
This might be a bit overkill but it should give you the ability to do the following:
#client = Client.create name: "x", etc etc
#slot = Slot.find_by day: "saturday", time: "1400"
#client.test_drives.create slot: #slot
You'd be able to add a validation to test_drive on whether a particular slot has been taken.
You'd also be able to add a validation to the slot model to determine which date/time combinations are permissible:
#app/models/slot.rb
class Slot < ActiveRecord::Base
...
validate :day_times
private
def day_times
permissible_times: {monday: [:0900, :1000], tuesday: [:1200]}
errors.add(:time, "Sorry, this time is unavailable on this day") unless permissible_times[day.to_sym].include? time
end
end
You can read up about enum here.
One of the main issues you have at the moment is that you've populated your TestDrives table with client_ fields. Whilst this will work, it's a faux pas - it will quickly become cumbersome and overburdened.
You'll be much better with a has_many :through association, as described above...
How about this approach.
Block
date
timeslot
TestDrive
block_id
user_id
client_title
client_name
client_firstname
client_company
client_street
client_house_nr
client_postal_code
client_city
client_email
client_phone
client_mobile
A Block model can be created either by the car dealer or by the appointment.
For example there can be a block with id=1. If there is a TestDrive that has block_id=1 then this is an appointment. If no TestDrive is found with block_id=1 then this is just a blocked slot.
So a Block has_one :test_drive and a TestDrive belongs_to :block. A TestDrive must be associated with a Block but a Block can have no TestDrives (Zero-to-many relationship)
create table foo (id, name, order, ...);
create table foo_bar (id, foo_id, name, value);
foo contains order column with values as (1,2,3,4,5,...10)
assuming foo_bar contains multiple records for each foo.
How do I delete foos whose order values are 3..6 and its dependent records?
class Foo < ActiveRecord::Base
has_many :foo_bars, :dependent => :destroy
end
class FooBar < ActiveRecord::Base
belongs_to :foo
end
If your relation is like above following code will work
Foo.delete_all(["id in (?)", [3,4,5,6]])
OR Just
Foo.delete([3,4,5,6])
Ref delete
EDITED
From little i know your question i think you have something like following
foo table
id some_column order
1 some_value 3
2 some_value 4
3 some_value 3
4 some_value 2
5 some_value 1
6 some_value 5
7 some_value 6
foo_bar table
id some_column foo_id
1 some_value 2
2 some_value 1
3 some_value 3
4 some_value 2
5 some_value 4
6 some_value 5
7 some_value 6
Then user following order instead of id
Foo.delete_all(["order in (?)", [3,4,5,6]])
EDITED: Use destroy_all to Destroys the records by instantiating each record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
Foo.destroy_all(["order in (?)", [3,4,5,6]])
The correct answer, to destroy(delete) the dependens:
As #Salil said if your model has the dependent callback like this:
has_many :foo_bars, :dependent => :destroy
Your query to destroy the parent and dependent records should be:
Foo.where(order: [1, 2, 3, 4]).destroy_all
The method where will get all records with the array of orders and call the destroy method for each record found.
JUST USE delete or delete_all if you don't want to execute the callbacks about destroy dependencies.
I need some help building a table and then getting data from that table in Rails 3.
Here's the break down:
Models - 3 models involved here they are:
Thread has many participants
Participants belong to thread
Users
Activity table:
id | thread_id | participants
Example records would look something like:
1 | 300 | 3,1,5,67,13
2 | 333 | 3,12
3 | 433 | 1,12
4 | 553 | 1,12, 67
Where participants, is a list of user_ids, if there is a better way to store the user_ids please let me know. I haven't built this yet.
After I populate the activity table. I then want to be able to query along the lines of:
Select all Activity records where the participant_id of 67 is included in the participants field.
I hope the above is clear, if not please let me know. Ideas? Thoughts? Suggestions.
Thanks
While it's tempting to store multiple values in a column, it always ends up with someone getting hurt. You're better off building a join table to relate the models.
For example you could do this:
class DiscussionThread < ActiveRecord::Base
has_many :participations
has_many :participants, :through => :participations
end
class Participation < ActiveRecord::Base
belongs_to :discussion_thread
belongs_to :participant, :class_name => "User", :foreign_key => :user_id
end
class User < ActiveRecord::Base
has_many :participations
has_many :dicussion_threads, :through => :participations
end
That gives you three tables:
table: discussion_threads
columns: id
table: participations
columns: id | discussion_thread_id | user_id
table: users
columns: id
To find the threads in which a user is participating, just do:
#user.discussion_threads
And to find the users participating in a thread:
#discussion_thread.participants
Note: Thread is a reserved word in Ruby, so I've renamed it DiscussionThread
EDIT
mind showing an example of how to serialize an array of ids and then query against them?
You awaken in the middle of the night, and under the power of a strange compulsion you go to your computer and create this migration:
rails g model Abomination horror_ids:text
and model:
class Abomination < ActiveRecord::Base
serialize :horror_ids
end
You test it to make sure it can store an array:
#abomination = Abomination.create(:horror_ids=>[2,33,42])
#abomination.horror_ids # => [2,33,42]
So what? You know that behind the scenes Rails converts it to YAML, which looks like this:
---\n
- 2\n
- 33\n
- 42\n
Again compelled by that wierd urging, you wonder "How could I search for a particular id stored in this column?". Well, it's just a string, right? You know how to find that in a text field:
cthulhu = 33
Abomination.where(["horror_ids LIKE '%- ?\n%'",cthulhu]).first
With increasing dread, you realize someone might stumble across this and think it was actually a good idea. It must be destroyed! But you are unable to type rm -rf *, instead the strange force makes you consider the quirks that a future mindless follower of Cthulhu developer might need to know, such as
#abomination = Abomination.create
#abomination.horror_ids # => nil
#abomination = Abomination.create(:horror_ids=>[])
#abomination.horror_ids # => []
#abomination = Abomination.create(:horror_ids=>"any string value can go here")
#abomination.horror_ids # => "any string value can go here"
And the fact that serialized data can get corrupted when the column size is too small to accommodate it all.
You make one last ditch effort to kick out the power cord, but it is too late, the gibbering, insane consciousness that has taken control of you posts the code on StackOverflow for the whole world to see. Finally you collapse into a troubled sleep. The next day, realizing what you've perpetrated, you give up coding forever and become an accountant.
Moral
Don't do this