Referencing Sequel's docs I've set up a one_to_one association between a Position and a Company.
class Position < Sequel::Model
one_to_one :company
end
class Company < Sequel::Model
many_to_one :position
end
When I try to get a company through a position I get nil, although I can find the company with a direct Sequel query.
p = Position.first #=> #<Position #values={:id=>1}>
p.company #=> nil
Company.where(position_id: p.id).first #=> #<Company #values={:id=>1, position_id: 1}>
You look confused about the relations or the schema. Unless you have a very specific business case, a company has many positions, which makes one-to-many relation and many positions can belong to one company, which makes many-to-one.
Here's how I see it:
require 'sqlite3'
require 'sequel'
DB = Sequel.connect('sqlite://companies')
DB.create_table :companies do
primary_key :id
String :name
end
DB[:companies].insert(name: 'Acme')
DB.create_table :positions do
primary_key :id
String :name
foreign_key :company_id, :companies
end
DB[:positions].insert(name: 'CEO', company_id: DB[:companies].first[:id])
DB[:positions].insert(name: 'CTO', company_id: DB[:companies].first[:id])
class Company < Sequel::Model
one_to_many :positions
end
class Position < Sequel::Model
many_to_one :company
end
p Company.first.positions
# [#<Position #values={:id=>1, :name=>"CEO", :company_id=>1}>, #<Position #values={:id=>2, :name=>"CTO", :company_id=>1}>]
p Position.first.company
# #<Company #values={:id=>1, :name=>"Acme"}>
I can get your associations to work:
require 'sequel'
DB = Sequel.connect('sqlite://test.db')
unless DB.table_exists? (:positions)
DB.create_table :positions do
primary_key :id
string :name
foreign_key :company_id
end
end
unless DB.table_exists?(:companies)
DB.create_table :companies do
primary_key :id
string :name
foreign_key :position_id
end
end
class Position < Sequel::Model
one_to_one :company
end
class Company < Sequel::Model
many_to_one :position
end
ford = Company.create(name: "Ford")
vw = Company.create(name: "VW")
accountant.company = ford
p accountant.company #=> #<Company #values={:id=>53, :name=>"Ford", :position_id=>35}>
puts accountant.id #=> 35
accountant.add_company(vw) #=> undefined method `add_company' for #<Position...
I'll add that the Sequel docs for associations are terrible. They need to include a complete example--including how to create the objects so that they are associated with each other.
Related
Using the ruby sequel gem, I have a small (sqlite3) database with the following schema:
db.create_table :item_types do
primary_key :id
String :name, unique: true, null: false
end
db.create_table :item_values do
primary_key :id
String :name, unique: true, null: false
end
db.create_table :items do
primary_key :id
foreign_key :type, :item_types, key: :id
foreign_key :value, :item_values, key: :id
String :name, unique: true, null: false
String :desc, null: false
end
Ideally, I'd like to be able to be able to use similar to
item = Item.first(...)
puts item.type.name, item.value.name
Reading through the association guide, it looks like I need a one_to_one association in the Item model for item_types and item_values. However, I'm not sure of the proper way to link them. Should the foreign key be passed via the key option, or is there a more appropriate method?
The guide illustrates setting up a one to many association with this example:
# Database schema:
# artists albums
# :id <----\ :id
# :name \----- :artist_id
# :name
class Artist < Sequel::Model
one_to_many :albums
end
class Album < Sequel::Model
many_to_one :artist
end
To be honest I have never used Sequel but from guide it seems like you should be doing something like:
class ItemType < Sequel::Model
one_to_many :items
end
class ItemValue < Sequel::Model
one_to_many :items
end
class Item < Sequel::Model
many_to_one :type, class: 'ItemType', key: 'item_type_id'
many_to_one :value, class: 'ItemValue', key: 'item_value_id'
end
Congratulations. You just ported the EAV anti-pattern to Sequel.
how to say? I do not understand what the sequel documentation tries to tell me about associations in case of a two models linked over a foreign key in one model A being a primary key in the other in a may_to_one case.
I always thought: If it is many_to _one in one direction it has to be one_to_many in the other... but the sequel provides a confusing chapter meant to clarify the topic with in addition an example I cannot follow.
It says in
"Differences Between many_to_one and one_to_one"
If you want to setup a 1-1 relationship between two models, where the foreign > key in one table references the associated table directly, you have to use
many_to_one in one model, and one_to_one in the other model. How do
you know which to use in which model? The simplest way to remember is
that the model whose table has the foreign key uses many_to_one, and
the other model uses one_to_one"
And continues to provide this strange example:
# Database schema:
# artists albums
# :id <----\ :id
# :name \----- :artist_id
# :name
class Artist
one_to_one :album
end
class Album
many_to_one :artist
end
In albums I may find several rows pointing to same artist... why shouldn't the artist point back to all his/her albums?
The sequel docu is crazy hard to read in many cases but this chapter reads easy but makes no sense for me:(
Same issue for me.
require "logger"
require "sequel"
db = Sequel.connect "postgres://localhost/postgres", :logger => Logger.new(STDOUT)
db.drop_table :artists, :cascade => true if db.table_exists?(:artists)
db.create_table :artists do
primary_key :id
foreign_key :album_id, :albums
end
db.drop_table :albums, :cascade => true if db.table_exists?(:albums)
db.create_table :albums do
primary_key :id
foreign_key :artist_id, :artists
end
class Artist < Sequel::Model(db[:artists])
one_to_one :album
end
class Album < Sequel::Model(db[:albums])
one_to_one :artist
end
artist_1 = Artist.create
album_1 = Album.create
artist_1.update :album => album_1
album_1.reload
puts album_1.artist.nil?
artist_2 = Artist.create
album_2 = Album.create
album_2.update :artist => artist_2
artist_2.reload
puts artist_2.album.nil?
We can fix this example by replacing any of one_to_one into many_to_one.
class Album
many_to_one :artist
end
In this case artist.album_id won't be used.
class Artist
many_to_one :albums
end
In this case album.artist_id won't be used.
The problem is that method names one_to_one and many_to_one were selected by underlying sequel logic and they are not user friendly.
You can create user friendly aliases for these methods. I prefer just to use it with comments. For example:
db.create_table :artists do
primary_key :id
foreign_key :album_id, :albums
end
db.create_table :albums do
primary_key :id
end
class Artist < Sequel::Model(db[:artists])
many_to_one :album # I have album_id foreign key
end
class Album < Sequel::Model(db[:albums])
one_to_one :artist # I don't have artist_id foreign key
end
I've the following two models:
class Dispute < ApplicationRecord
belongs_to :accuser, class_name: 'User', optional: true
belongs_to :defendant, class_name: 'User', optional: true
end
class User < ApplicationRecord
end
Here's the migration for Dispute:
class CreateDisputes < ActiveRecord::Migration[5.0]
def change
create_table :disputes do |t|
t.references :accuser
t.references :defendant
end
end
end
This is how they behave in Rails:
Dispute.first.accuser
# => <# User>
Dispute.first.defendant
# => <# User>
In Sequel, I'm supposed to use many_to_one, but does that mean that Sequel User model should have a corresponding one_to_many? Can't seem to get it to work.
This should work:
Sequel.migration do
change do
create_table(:disputes) do
primary_key :id
foreign_key :accuser_id, :users
foreign_key :defendant_id, :users
end
end
end
class Dispute < Sequel::Model
many_to_one :accuser, :class=>:User
many_to_one :defendant, :class=>:User
end
Assuming:
a many_to_many association defined in the many_to_many section of the Sequel documentation; artists --< albums_artists >-- albums
albums_artists has a foo field
How do I access the fields on the albums_artists table in a WHERE clause? This syntax doesn't work:
<% #artist.albums_dataset.where(:foo => 'bar').each do |album| %>
...
<%= album[:foo] %>
...
<% end %>
If I were to add a model for album_artists, creating two associations in the process, would I remove the existing many_to_many association?
You need to qualify the table name for the column you want to access, using the syntax :tablename__columnname (two underscores).
#artist.albums_dataset.filter(:albums_artists__foo => "bar").each do |album|
Here's a test, also showing the generated SQL:
require 'sequel'; DB = Sequel.sqlite
DB.create_table(:artists){ primary_key :id; String :name }
DB.create_table(:albums){ primary_key :id; String :name }
DB.create_table(:albums_artists) do
String :foo
foreign_key :artist_id, :artists
foreign_key :album_id, :albums
primary_key [:artist_id, :album_id]
end
class Artist < Sequel::Model; many_to_many :albums; end
class Album < Sequel::Model; many_to_many :artists; end
Artist.multi_insert [ {name:"Bob"}, {name:"Dylan"}, {name:"Joni"} ]
Album.multi_insert [ {name:"BobSolo"}, {name:"JoniSolo"},
{name:"BobJoni"}, {name:"BobDylan"},
{name:"BobJoniDylan"} ]
# If the album name includes the artist, add them to it.
Artist.each do |ar| Album.each do |al|
if al.name.include?(ar.name)
DB[:albums_artists] << {artist_id:ar.id, album_id:al.id}
end
end end
bob.albums.map(&:name)
#=> ["BobSolo", "BobJoni", "BobDylan", "BobJoniDylan"]
bob.albums_dataset.filter(albums_artists__foo:"bar").sql
#=> SELECT `albums`.*
#=> FROM `albums` INNER JOIN `albums_artists`
#=> ON (`albums_artists`.`album_id` = `albums`.`id`)
#=> WHERE ((`albums_artists`.`artist_id` = 1)
#=> AND (`albums_artists`.`foo` = 'bar'))
bob.albums_dataset.filter(albums_artists__foo:"bar").all
#=> [#<Album #values={:id=>5, :name=>"BobJoniDylan"}>]
Assuming I have something like this:
# Schema:
DB.create_table :transactions do
primary_key :id
foreign_key :card_id, :cards
Integer :amount
end
DB.create_table :cards do
primary_key :id
foreign_key :transaction_id, :transactions
Intger :number
end
# Models:
class Transaction < Sequel::Model
one_to_one :card
end
class Card < Sequel::Model
one_to_one :transaction
end
How do I make this work, such that it saves both trans, card, and their respective associations?
trans = Transaction.new(:amount => 100)
card = Card.new(:number => 4000500060007000)
trans.card = card
trans.save
As it stands, this doesn't work because card isn't saved first, and Sequel throws a "no primary key" error. If I save the card first, it won't get the transaction's id.
Basically, I'm trying to avoid this:
# Save without associations first, but this will assign primary keys
trans.save
card.save
# Now, manually create associations
trans.card = card
card.trans = trans
# Re-save again, this time with associations
trans.save
card.save
You may want to try and change the association type to something more like:
# Schema:
DB.create_table :transactions do
primary_key :id
Integer :amount
end
DB.create_table :cards do
primary_key :id
foreign_key :transaction_id, :transactions
Integer :number
end
# Models:
class Transaction < Sequel::Model
one_to_many :card
end
class Card < Sequel::Model
one_to_one :transaction
end
Now you create as:
trans = Transaction.new(:amount => 100)
trans.save
trans.add_card(:number => 4000500060007000)
This will allow for all the same options as well as permitting (but certainly not requiring) a transaction to be split across multiple cards.