I have this (simplified) concern:
module Nobi::Personable
extend ActiveSupport::Concern
included do
belongs_to :person, :autosave => true delegate :gender,
:gender=, :gender_changed?, :gender_was, :to => :person, :allow_nil => true
enum gender: { male: "male", female: "female" }
end
end
A Resident has this concern.
Now when I do this:
2.6.6> Resident.last.gender
Resident Load (16.2ms) SELECT "residents".* FROM "residents" ORDER BY "residents"."id" DESC LIMIT $1 [["LIMIT", 1]]
Person Load (16.1ms) SELECT "people".* FROM "people" WHERE "people"."id" = $1 LIMIT $2 [["id", 48], ["LIMIT", 1]]
=> "male"
However when I ask: male? I get:
2.6.6> Resident.last.male?
Resident Load (17.0ms) SELECT "residents".* FROM "residents" ORDER BY "residents"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> false
How is this possible?
If I include the enum on the Person model, it works fine:
Person.last.male?
Person Load (15.9ms) SELECT "people".* FROM "people" ORDER BY "people"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> true
I've created a minimalistic demo app which demonstrates this behavior:
https://github.com/rept/enum_app
r = Resident.create(gender: :male, local_gender: :local_male)
r.local_male?
=> true
r.male?
=> false
Declare an enum attribute where the values map to integers in the
database, but can be queried by name.
-- ActiveRecord::Enum
You either need to use an integer column with your enum or declare the mapping explicitly. Rails assumes that the values stored in the database are equal to the indices of the array passed to enum. Since "male" != 0 #male? will return false.
module Nobi::Personable
extend ActiveSupport::Concern
included do
belongs_to :person, :autosave => true
enum gender: {
male: "male",
female: "female"
}
end
end
While using a string column kind of defeats the point of using an enum in the first place declaring the mapping explicitly is seen as a best practice as it will prevent hard to debug breakages that can be caused by simply reordering the enum values in the array.
And if you need proof:
class User < ApplicationRecord
enum gender: [:male, :female] # users.gender is an integer column
end
irb(main):003:0> User.first
(0.9ms) SELECT sqlite_version(*)
User Load (2.0ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<User id: 9, created_at: "2020-10-29 09:24:12", updated_at: "2020-10-29 09:24:12", gender: "male">
irb(main):004:0> User.first.male?
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> true
class Resident < ApplicationRecord
# residents.gender is a string column
enum gender: {
male: 'male',
female: 'female'
}
end
irb(main):001:0> Resident.create!(gender: 'male')
(0.4ms) SELECT sqlite_version(*)
(0.1ms) begin transaction
Resident Create (1.6ms) INSERT INTO "residents" ("gender", "created_at", "updated_at") VALUES (?, ?, ?) [["gender", "male"], ["created_at", "2020-10-29 09:56:59.471917"], ["updated_at", "2020-10-29 09:56:59.471917"]]
(4.7ms) commit transaction
=> #<Resident id: 1, gender: "male", created_at: "2020-10-29 09:56:59", updated_at: "2020-10-29 09:56:59">
irb(main):002:0> Resident.first.male?
Resident Load (0.2ms) SELECT "residents".* FROM "residents" ORDER BY "residents"."id" ASC LIMIT ? [["LIMIT", 1]]
=> true
Related
I have 3 models : User, Conversation and ConversationUser
class User < ApplicationRecord
has_many :conversation_users, dependent: :destroy
has_many :conversations, through: :conversation_users
end
class Conversation < ApplicationRecord
has_many :conversation_users, dependent: :destroy
has_many :users, through: :conversation_users
end
class ConversationUser < ApplicationRecord
belongs_to :conversation
belongs_to :user
end
My purpose
I would like to find a Conversation when 2 users have the same conversation (a conversation must only have 2 conversation_users with the id of these 2 users)
For the test
I only created ONE conversation with TWO conversation_users
conversation = Conversation.create
conversation.conversation_users.create(user: User.first)
conversation.conversation_users.create(user: User.second)
My Problem
I want to find the conversation with JUST the first User AND second User.
If I do the following query, Active Record will still show me a conversation because it's doing a OR clause but I need a AND.
The correct result should show me an empty array because there is not a conversation with User.first AND User.third
Conversation.joins(:conversation_users).where(conversation_users: {user_id: [User.first.id, User.third.id]})
User Load (3.3ms) SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1 [["LIMIT", 1]]
User Load (2.5ms) SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1 OFFSET $2 [["LIMIT", 1], ["OFFSET", 2]]
Conversation Load (3.9ms) SELECT "conversations".* FROM "conversations" INNER JOIN "conversation_users" ON "conversation_users"."conversation_id" = "conversations"."id" WHERE "conversation_users"."user_id" IN ($1, $2) LIMIT $3 [["user_id", "274d2f54-2418-4dec-a696-a5f62196ee85"], ["user_id", "0ef2f797-3518-4096-951b-1837ca28e4e4"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Conversation id: "35556705-a162-4ee4-9776-963ae87bcfa8", created_at: "2020-04-20 09:34:05", updated_at: "2020-04-20 09:34:05">]>
Expectations
Should find a Conversation with User.first and User.second
Should NOT find a Conversation with User.first and User.third
I tried this query for the first expectation but it is returning an empty array
Conversation.joins(:conversation_users).where(conversation_users: {user_id: User.first.id}).where(conversation_users: {user_id: User.second.id})
User Load (3.8ms) SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1 [["LIMIT", 1]]
User Load (4.0ms) SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1 OFFSET $2 [["LIMIT", 1], ["OFFSET", 1]]
Conversation Load (3.3ms) SELECT "conversations".* FROM "conversations" INNER JOIN "conversation_users" ON "conversation_users"."conversation_id" = "conversations"."id" WHERE "conversation_users"."user_id" = $1 AND "conversation_users"."user_id" = $2 LIMIT $3 [["user_id", "274d2f54-2418-4dec-a696-a5f62196ee85"], ["user_id", "44a0c35a-b5c1-4bb6-a8a3-30ae3d4b3345"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
class Conversation < ApplicationRecord
has_many :user_conversations
has_many :users, through: :user_conversations
def self.between(*users)
users.map do |u|
where("EXISTS(SELECT * FROM user_conversations uc WHERE uc.conversation_id = conversations.id AND uc.user_id = ?)", u)
end.reduce(&:merge)
.joins(:user_conversations)
.group(:id)
.having('count(*) = ?', users.length)
end
end
Not the cleanest solution ever but what is does is check for the existance of a join record for each user and then .having('count(*) = ?', users.length) ensures that there are not more join records then users.
Example usage:
Conversation.between(User.first, User.second)
Conversation.between(1,2,3)
Conversation.between(*User.all) # splat it like a pro
This results in the following SQL query:
SELECT "conversations".* FROM "conversations"
INNER JOIN "user_conversations" ON "user_conversations"."conversation_id" = "conversations"."id"
WHERE
(EXISTS(SELECT * FROM user_conversations uc WHERE uc.conversation_id = conversations.id AND uc.user_id = 1))
AND
(EXISTS(SELECT * FROM user_conversations uc WHERE uc.conversation_id = conversations.id AND uc.user_id = 2))
GROUP BY "conversations"."id"
HAVING (count(*) = 2)
LIMIT $1
You can do a query with AND condition as follows
Conversation.joins(:conversation_users).where(conversation_users: {user_id: User.first.id}).where(conversation_users: {user_id: User.second.id})
I have a User model and a Spkr model.
User:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :tlks, dependent: :destroy
has_many :spkrs, dependent: :destroy
has_many :msgs, dependent: :destroy
has_one_attached :image
extend FriendlyId
friendly_id :slug_candidates, use: :slugged
def slug_candidates
[
:username,
[:username, DateTime.now.to_date]
]
end
end
Spkr
class Spkr < ApplicationRecord
belongs_to :tlk
belongs_to :user
has_many :msgs, dependent: :destroy
has_one_attached :image
end
When my user model has an image attached, when a spkr is made I want it to have the same image attached to it as the user generating the spkr.
I have a SpkrMaker module:
module SpkrMaker
def make_spkr
spkr = Spkr.create!(
user: current_user,
tlk: #tlk,
name: current_user.username,
bio: current_user.bio,
)
if current_user.image.present?
ActiveStorage::Attachment.create(
name: 'image',
record_type: 'Spkr',
record_id: spkr.id,
blob_id: current_user.image.id
)
end
end
end
This is called during the flow, when it is called my server logs state:
Started POST "/tlks" for ::1 at 2020-01-11 10:39:41 +0000
Processing by TlksController#create as JS
Parameters: {"authenticity_token"=>"1NJc0ZSwo8hL1JHw5karkWNxoWRzHPNx/xecaAQHdN+EdsG/o+yZwdxZXLZLPVbkgiPiZZX6PpaF38VX5etTAw==", "tlk"=>{"title"=>"goooolan"}, "commit"=>"Create Tlk"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 23], ["LIMIT", 1]]
(0.1ms) begin transaction
↳ app/controllers/tlks_controller.rb:34:in `create'
Tlk Exists? (0.3ms) SELECT 1 AS one FROM "tlks" WHERE "tlks"."id" IS NOT NULL AND "tlks"."slug" = ? LIMIT ? [["slug", "goooolan"], ["LIMIT", 1]]
↳ app/controllers/tlks_controller.rb:34:in `create'
Tlk Create (0.4ms) INSERT INTO "tlks" ("user_id", "title", "created_at", "updated_at", "slug", "invite_code") VALUES (?, ?, ?, ?, ?, ?) [["user_id", 23], ["title", "goooolan"], ["created_at", "2020-01-11 10:39:41.866979"], ["updated_at", "2020-01-11 10:39:41.866979"], ["slug", "goooolan"], ["invite_code", 469667]]
↳ app/controllers/tlks_controller.rb:34:in `create'
(0.9ms) commit transaction
↳ app/controllers/tlks_controller.rb:34:in `create'
(0.1ms) begin transaction
↳ lib/spkr_maker.rb:3:in `make_spkr'
Spkr Create (0.5ms) INSERT INTO "spkrs" ("tlk_id", "user_id", "name", "bio", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?) [["tlk_id", 135], ["user_id", 23], ["name", "test"], ["bio", "info about me"], ["created_at", "2020-01-11 10:39:41.871219"], ["updated_at", "2020-01-11 10:39:41.871219"]]
↳ lib/spkr_maker.rb:3:in `make_spkr'
(0.8ms) commit transaction
↳ lib/spkr_maker.rb:3:in `make_spkr'
ActiveStorage::Attachment Load (0.1ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 23], ["record_type", "User"], ["name", "image"], ["LIMIT", 1]]
↳ lib/spkr_maker.rb:9:in `make_spkr'
(0.0ms) begin transaction
↳ lib/spkr_maker.rb:10:in `make_spkr'
Spkr Load (0.2ms) SELECT "spkrs".* FROM "spkrs" WHERE "spkrs"."id" = ? LIMIT ? [["id", 104], ["LIMIT", 1]]
↳ lib/spkr_maker.rb:10:in `make_spkr'
ActiveStorage::Blob Load (0.0ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ? [["id", 51], ["LIMIT", 1]]
↳ lib/spkr_maker.rb:10:in `make_spkr'
(0.0ms) rollback transaction
↳ lib/spkr_maker.rb:10:in `make_spkr'
Completed 422 Unprocessable Entity in 25ms (ActiveRecord: 3.7ms | Allocations: 14233)
ActiveRecord::RecordInvalid (Validation failed: Blob must exist):
lib/spkr_maker.rb:10:in `make_spkr'
app/controllers/tlks_controller.rb:36:in `create
When I run User.last.image in the rails console, I get the following:
irb(main):002:0> User.last.image
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ? [["LIMIT", 1]]
=> #<ActiveStorage::Attached::One:0x00007f88a21cda90 #name="image", #record=#<User id: 23, email: "j#test.com", username: "test", bio: "info about me", name: nil, created_at: "2020-01-11 01:56:22", updated_at: "2020-01-11 01:56:48", slug: "test">>
irb(main):003:0>
I do not know what the problem is, and am not good enough at understanding the server logs to work out what is going wrong.
A Spkr is made during the process, so everything above line 10 is working in the SpkrMaker module (ActiveStorage::Attachment.create( = line 10).
OK Further information as of this morning...(11/01/2020)
irb(main):010:0> User.last.image.id
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ? [["LIMIT", 1]]
ActiveStorage::Attachment Load (0.2ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 23], ["record_type", "User"], ["name", "image"], ["LIMIT", 1]]
=> 51
But
irb(main):011:0> ActiveStorage::Blob.last.id
ActiveStorage::Blob Load (0.2ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" ORDER BY "active_storage_blobs"."id" DESC LIMIT ? [["LIMIT", 1]]
=> 49
I don't know why there is this difference.
While attaching the image to Spkr you are tagging active storage attachment id of current_user instead of blob_id instead of blob id I have update the code.
module SpkrMaker
def make_spkr
spkr = Spkr.create!(
user: current_user,
tlk: #tlk,
name: current_user.username,
bio: current_user.bio,
)
if current_user.image.present?
ActiveStorage::Attachment.create(
name: 'image',
record_type: 'Spkr',
record_id: spkr.id,
blob_id: current_user.image.blob_id
)
end
end
end
It should work now.
I have a Rails (5.2.2) app (Postgres database) with some Models related to different geographies:
- districts (have many sectors)
-- sectors (have many cells, have one district)
--- cells (have many villages, have one sector)
---- villages (have many facilities, have one cell)
----- facilities (have one village)
I also have a Report Model, which, for context, records the quantity of specific technology distributed in a specific location.
#<Report id: nil, date: nil, technology_id: nil, user_id: nil, contract_id: nil, model_gid: nil, distributed: nil, checked: nil, created_at: nil, updated_at: nil, people: nil, households: nil>
This location can be any of the geography models. So I'm using GlobalID stored as a string in model_gid on the Report record.
e.g.:
#<Report id: 1, ... model_gid: "gid://liters-tracker/Village/64", ...>
Then I wrote some scopes that work fine:
scope :only_districts, -> { where('model_gid ILIKE ?', '%/District/%') }
scope :only_sectors, -> { where('model_gid ILIKE ?', '%/Sector/%') }
scope :only_cells, -> { where('model_gid ILIKE ?', '%/Cell/%') }
scope :only_villages, -> { where('model_gid ILIKE ?', '%/Village/%') }
scope :only_facilities, -> { where('model_gid ILIKE ?', '%/Facility/%') }
I thought this was a good approach because my report.model method works:
def model
GlobalID::Locator.locate model_gid
end
e.g.:
2.4.5 :001 > Report.first.model
Report Load (0.5ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT $1 [["LIMIT", 1]]
Village Load (0.4ms) SELECT "villages".* FROM "villages" WHERE "villages"."id" = $1 LIMIT $2 [["id", 64], ["LIMIT", 1]]
=> #<Village id: 64, name: "Ruhanga", cell_id: 11, gis_id: 13080406, latitude: -2.00828333333333, longitude: 30.1708, population: 518, households: 179, created_at: "2019-01-21 22:53:06", updated_at: "2019-01-21 22:53:06">
I opted to do this string field instead of a polymorphic association because GlobalID::Locator methods can accept strings and parse out the model and ID from it. So why hassle with the association? Maybe this is the fundamental flaw in my thinking?
Because finding records based upon the model_gid seems to fail:
2.4.5 :045 > Report.all.where(model_gid: Report.first.model_gid)
Report Load (0.4ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT $1 [["LIMIT", 1]]
Report Load (0.5ms) SELECT "reports".* FROM "reports" WHERE "reports"."model_gid" = $1 LIMIT $2 [["model_gid", "--- gid://liters-tracker/Village/64\n"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
(honestly not sure why the SQL is morphed into "--- gid://liters-tracker/Village/64\n" and if this is actually my problem)
2.4.5 :046 > Report.all.where("model_gid ILIKE ?", Report.first.model_gid)
Report Load (0.5ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT $1 [["LIMIT", 1]]
Report Load (3.2ms) SELECT "reports".* FROM "reports" WHERE (model_gid ILIKE 'gid://liters-tracker/Village/64') LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
2.4.5 :049 > Report.all.where("model_gid = ?", Report.first.model_gid)
Report Load (0.3ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT $1 [["LIMIT", 1]]
Report Load (0.6ms) SELECT "reports".* FROM "reports" WHERE (model_gid = 'gid://liters-tracker/Village/64') LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
I'm trying to get this method to work:
def self.related_to(record)
where(model_gid: record.to_global_id.to_s)
end
And I really don't understand why it's not working:
2.4.5 :010 > Report.first.model_gid
Report Load (0.6ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> "gid://liters-tracker/Village/64"
2.4.5 :011 > Village.find(64).to_global_id.to_s
Village Load (0.5ms) SELECT "villages".* FROM "villages" WHERE "villages"."id" = $1 LIMIT $2 [["id", 64], ["LIMIT", 1]]
=> "gid://liters-tracker/Village/64"
2.4.5 :012 > Report.first.model_gid == Village.find(64).to_global_id.to_s
Report Load (0.4ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT $1 [["LIMIT", 1]]
Village Load (0.3ms) SELECT "villages".* FROM "villages" WHERE "villages"."id" = $1 LIMIT $2 [["id", 64], ["LIMIT", 1]]
=> true
2.4.5 :013 > Report.all.where(model_gid: Village.find(64).to_global_id.to_s)
Village Load (0.4ms) SELECT "villages".* FROM "villages" WHERE "villages"."id" = $1 LIMIT $2 [["id", 64], ["LIMIT", 1]]
Report Load (0.4ms) SELECT "reports".* FROM "reports" WHERE "reports"."model_gid" = $1 LIMIT $2 [["model_gid", "--- gid://liters-tracker/Village/64\n"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
If I mimic the scopes, it does work:
def self.related_to(record)
where('model_gid ILIKE ?', "%#{record.to_global_id.to_s}%")
end
But, in the example records I've been showing, this would match Village #64 and Village #640, so it's not a good solution.
UPDATE
I thought maybe the special characters were throwing things off. But things work as expected when I use another string column on another Model:
2.4.5 :052 > Village.first.update(name: "gid://liters-tracker/Village/64")
Village Load (0.5ms) SELECT "villages".* FROM "villages" ORDER BY "villages"."id" ASC LIMIT $1 [["LIMIT", 1]]
(0.2ms) BEGIN
Cell Load (0.2ms) SELECT "cells".* FROM "cells" WHERE "cells"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Village Exists (0.3ms) SELECT 1 AS one FROM "villages" WHERE "villages"."gis_id" = $1 AND "villages"."id" != $2 LIMIT $3 [["gis_id", 11070101], ["id", 1], ["LIMIT", 1]]
Village Update (0.3ms) UPDATE "villages" SET "name" = $1, "updated_at" = $2 WHERE "villages"."id" = $3 [["name", "gid://liters-tracker/Village/64"], ["updated_at", "2019-07-06 22:16:38.585563"], ["id", 1]]
(1.2ms) COMMIT
=> true
2.4.5 :053 > Village.where(name: "gid://liters-tracker/Village/64")
Village Load (0.3ms) SELECT "villages".* FROM "villages" WHERE "villages"."name" = $1 LIMIT $2 [["name", "gid://liters-tracker/Village/64"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Village id: 1, name: "gid://liters-tracker/Village/64", cell_id: 1, gis_id: 11070101, latitude: -2.054922, longitude: 30.0912883, population: 513, households: 110, created_at: "2019-01-21 22:53:04", updated_at: "2019-07-06 22:16:38">]>
I thought maybe I needed an index on the Report.model_gid field. But it hasn't made a difference.
class AddModelGidIndexToReports < ActiveRecord::Migration[5.2]
def change
add_index :reports, :model_gid
end
end
UPDATE 2
(this is based upon my own provided 'answer', but since it's a question, I put it here)
#MichaelChaney:
Just so I'm clear, are you suggesting something like this:
class Report < ApplicationRecord
belongs_to :technology, inverse_of: :reports
belongs_to :user, inverse_of: :reports
belongs_to :contract, inverse_of: :reports
enum geography: { district: 'district', sector: 'sector', cell: 'cell', village: 'village', facility: 'facility' }
At this point, should I just add a geography_id integer column and stop using GlobalID?
What about just going polymorphic instead?
For the sake of closing the record, I switched to a polymorphic association. Probably not quite as fast as the Enum solution that #MichaelChaney suggests in the comments on the previous answer, but fast enough for my in-house app and creates an association known to my app.
class Report < ApplicationRecord
belongs_to :technology, inverse_of: :reports
belongs_to :user, inverse_of: :reports
belongs_to :contract, inverse_of: :reports
# serialize :model_gid #<-- this was real bad as #MichaelChaney points out
# enum geography: { district: 'district', sector: 'sector', cell: 'cell', village: 'village', facility: 'facility' } #<-- this is probably the fastest option
belongs_to :reportable, polymorphic: true #<-- this is probably the middle ground, as the :reportable_id and :reportable_type columns are indexed together
And this was partnered with the following on all my Geography models, e.g.:
class Facility < ApplicationRecord
belongs_to :village, inverse_of: :facilities
has_one :cell, through: :village, inverse_of: :facilities
has_one :sector, through: :cell, inverse_of: :facilities
has_one :district, through: :sector, inverse_of: :facilities
has_many :reports, as: :reportable, inverse_of: :reportable #<-- tadaa
So now I don't even need my initial method as I can compare the results of reports.reportable with the record I have to see if they are associated.
The lesson I learned: in the early stages, I need to think more about RdBMS and what associations I'll care a lot about so I don't try doing dumb Regex searches across my dB.
The other lesson: keep better notes in my code base, so when I change strategies I can correctly un-wind things I implemented.
Ugh. This is a face-palm moment.
class Report < ApplicationRecord
belongs_to :technology, inverse_of: :reports
belongs_to :user, inverse_of: :reports
belongs_to :contract, inverse_of: :reports
serialize :model_gid
scope :only_districts, -> { where('model_gid ILIKE ?', '%/District/%') }
...
Report.model_gid is serialized, which I did before I discovered GlobalID. I think I was planning to save some key-value hash like {model: 'Village', id: '64'}.
Now to figure out how to un-serialize a column.
I have two tables, "users" and "friendships", I'm trying to make a relationship so that a user can have a friendship with other users, so generate this model friendships with rails g model Friendship user:references friend:references status
in the model friendship.rb:
class Friendship < ApplicationRecord
belongs_to :user
belongs_to :friend,class_name: "User"
end
but at the time of creating the record it shows me the following error:
Friendship.create(user_id: 1, friend_id:2, id: 1)
(0.1ms) begin transaction
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
SQL (0.4ms) INSERT INTO "friendships" ("id", "user_id", "friend_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["id", 1], ["user_id", 1], ["friend_id", 2], ["created_at", "2018-02-26 19:39:57.865042"], ["updated_at", "2018-02-26 19:39:57.865042"]]
(0.1ms) rollback transaction
Traceback (most recent call last):
1: from (irb):2
ActiveRecord::StatementInvalid (SQLite3::SQLException: no such table: main.friends: INSERT INTO "friendships" ("id", "user_id", "friend_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?))
irb(main):003:0>
What could it be? I have Rails 5.1.5
Your Friendship model is set up correctly, but perhaps your migration is not. It should look like this:
class CreateFriendships < ActiveRecord::Migration[5.1]
def change
create_table :friendships do |t|
t.references :user, index: true, foreign_key: true
t.references :friend, index: true, foreign_key: { to_table: :users }
end
end
end
I've set this up in a new Rails 5.1.5 project and it is working for me:
>> User.create(name: 'First User')
>> User.create(name: 'Second User')
>> Friendship.create(user_id: 1, friend_id:2, id: 1)
(0.1ms) begin transaction
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
SQL (0.8ms) INSERT INTO "friendships" ("id", "user_id", "friend_id") VALUES (?, ?, ?) [["id", 1], ["user_id", 1], ["friend_id", 2]]
(0.8ms) commit transaction
#<Friendship id: 1, user_id: 1, friend_id: 2>
Incidentally, you should avoid assigning id numbers to non-persisted objects. Better to let the database handle that job. I would create a Friendship simply as:
>> Friendship.create(user_id: 1, friend_id: 2)
I'm new to rails and I want to know how to fetch a one-to-one relationship. I want to fetch users city. In my postgresql database I have:
cities Table:
city:varchar
zipcode: integer
users Table
name:varchar
city_id:int
and in city and user model I have:
class City < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :city
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
I tried the following in my search controller but didnt work, when logged in:
current_user.city
I get the following error
Processing by SearchController#index as HTML
Parameters: {"utf8"=>"✓", "q"=>"", "criteria"=>"1", "commit"=>"Search"}
User Load (1.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 6 ORDER BY "users"."id" ASC LIMIT 1
PG::UndefinedColumn: ERROR: column cities.user_id does not exist
LINE 1: SELECT "cities".* FROM "cities" WHERE "cities"."user_id" =...
^
: SELECT "cities".* FROM "cities" WHERE "cities"."user_id" = $1 LIMIT 1
Completed 500 Internal Server Error in 11ms
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column cities.user_id does not exist
LINE 1: SELECT "cities".* FROM "cities" WHERE "cities"."user_id" =...
^
: SELECT "cities".* FROM "cities" WHERE "cities"."user_id" = $1 LIMIT 1):
why am I suppose to add a user_id column to cities table, when I have cities foreign key in users table? I dont want to add user_id into cities table.
You can use has_one :through association with join table. Some example for you below.
user model:
class User < ActiveRecord::Base
has_one :city, through: :user_city
has_one :user_city
end
city model:
class City < ActiveRecord::Base
belongs_to :user
end
user city join model:
class UserCity < ActiveRecord::Base
belongs_to :city
belongs_to :user
end
migration for join tables:
class JoinUserCity < ActiveRecord::Migration
def change
create_table :user_cities do |t|
t.integer :user_id
t.integer :city_id
end
end
end
Test in rails console:
=> u = User.create
(0.1ms) begin transaction
SQL (0.5ms) INSERT INTO "users" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2014-12-07 15:47:14.595728"], ["updated_at", "2014-12-07 15:47:14.595728"]]
(3.3ms) commit transaction
=> #<User id: 4, created_at: "2014-12-07 15:47:14", updated_at: "2014-12-07 15:47:14">
=> u.city
City Load (0.2ms) SELECT "cities".* FROM "cities" INNER JOIN "user_cities" ON "cities"."id" = "user_cities"."city_id" WHERE "user_cities"."user_id" = ? LIMIT 1 [["user_id", 4]]
=> nil
=> c = City.create
(0.1ms) begin transaction
SQL (0.5ms) INSERT INTO "cities" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2014-12-07 15:47:24.535039"], ["updated_at", "2014-12-07 15:47:24.535039"]]
(3.3ms) commit transaction
=> #<City id: 1, created_at: "2014-12-07 15:47:24", updated_at: "2014-12-07 15:47:24">
irb(main):004:0> u.city = c
UserCity Load (0.3ms) SELECT "user_cities".* FROM "user_cities" WHERE "user_cities"."user_id" = ? LIMIT 1 [["user_id", 4]]
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "user_cities" ("city_id", "user_id") VALUES (?, ?) [["city_id", 1], ["user_id", 4]]
(1.0ms) commit transaction
=> #<City id: 1, created_at: "2014-12-07 15:47:24", updated_at: "2014-12-07 15:47:24">
irb(main):005:0> u.save
(0.1ms) begin transaction
(0.1ms) commit transaction
=> true
=> u = User.last
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
=> #<User id: 4, created_at: "2014-12-07 15:47:14", updated_at: "2014-12-07 15:47:14">
=> u.city
City Load (0.2ms) SELECT "cities".* FROM "cities" INNER JOIN "user_cities" ON "cities"."id" = "user_cities"."city_id" WHERE "user_cities"."user_id" = ? LIMIT 1 [["user_id", 4]]
=> #<City id: 1, created_at: "2014-12-07 15:47:24", updated_at: "2014-12-07 15:47:24">
take a look at the document of has_one and belogns_to,
belongs_to(name, options = {})
Specifies a one-to-one association with another class. This method should only be used if this class
contains the foreign key. If the other class contains the foreign key, then you should use has_one
instead.
as the user table has the foreign key, you should change your model definition like this
class City < ActiveRecord::Base
has_one :user
end
class User < ActiveRecord::Base
belongs_to :city
end