I've applied uniqueness validation to "appeal_id" in model named "Campaigns". It asks me to add a unique index for uniqueness validation, I added campaign_id as a unique index. But it still shows the same error.
app/models/campaign.rb:9:3: C: Rails/UniqueValidationWithoutIndex: Uniqueness validation should have a unique index on the database column.
validates :appeal_id, uniqueness: { scope: :user_id }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
My schema for campaigns table looks like following:
create_table "campaigns", force: :cascade do |t|
t.string "title"
t.text "description"
t.bigint "appeal_id", null: false
t.bigint "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "campaign_id"
t.index ["appeal_id"], name: "index_campaigns_on_appeal_id"
t.index ["campaign_id"], name: "index_campaigns_on_campaign_id", unique: true
t.index ["user_id"], name: "index_campaigns_on_user_id"
end
"campaign.rb" file is as follows:
class Campaign < ApplicationRecord
has_many :donations, dependent: :destroy
belongs_to :appeal
belongs_to :user
validates :title, presence: true
validates :description, presence: true
validates :appeal_id, uniqueness: { scope: :user_id }
end
You're missing the compound index that you actually need to ensure that the combination of the two columns is unique.
Adding it is relatively simple:
class AddUniqueCompoundIndexToCampaigns < ActiveRecord::Migration[7.0]
def change
add_index [:appeal_id, :user_id], unique: true
end
end
I dropped my database and then edited my create_campaign migration and added this line of code
add_index :campaigns, [:appeal_id, :user_id], unique: true
in my migration. Then run the command
rails db:create db:migrate
which actually created my database again and resolved the issue of unique index.
class CreateCampaigns < ActiveRecord::Migration[7.0]
def change
create_table :campaigns do |t|
t.string :title
t.text :description
t.references :appeal, null: false, foreign_key: true
t.references :user, null: false, foreign_key: true
t.timestamps
end
add_index :campaigns, [:appeal_id, :user_id], unique: true
end
end
I'm pretty new to Ruby and having some trouble with how I would approach decrementing info in a db using a has_many :through table. My tables are food_product, menu_item and sale what I'm wanting to have happen is that you can click on a sell this item for the menu_item and have it decrement the amount of orders in the food_product database. Here's the code that I have currently:
FoodProduct model
class FoodProduct < ApplicationRecord
has_many :sales
has_many :menu_items, :through => :sales
validates :food_, presence: true
validates :amount_ordered_, presence: true
validates :amount_to_sell_, presence: true
validates :amount_of_pans_, presence: true
validates :date_ordered_, presence: true
validates :date_order_arrives_, presence: true
validates :soft_out_date_, presence: true
validates :hard_out_date_, presence: true
end
menu_item model
class MenuItem < ApplicationRecord
has_many :sales
has_many :food_products, :through => :sales
end
sale model
class Sale < ApplicationRecord
belongs_to :food_product
belongs_to :menu_item
end
and my schema
create_table "food_products", force: :cascade do |t|
t.string "food_"
t.integer "amount_ordered_"
t.integer "amount_to_sell_"
t.integer "amount_of_pans_"
t.integer "orders_per_pan_"
t.datetime "date_ordered_"
t.datetime "date_order_arrives_"
t.datetime "soft_out_date_"
t.datetime "hard_out_date_"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "menu_items", force: :cascade do |t|
t.string "name_"
t.string "food_item1_"
t.string "food_item2_"
t.string "food_item3_"
end
create_table "sales", force: :cascade do |t|
t.bigint "food_product_id"
t.bigint "menu_item_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["food_product_id"], name: "index_sales_on_food_product_id"
t.index ["menu_item_id"], name: "index_sales_on_menu_item_id"
end
If there's anything else I need to share let me know, I'm not sure of what all info is needed by y'all to help me
I think so you should add after_create callback in Sale class.Like this
class Sale < ApplicationRecord
belongs_to :food_product
belongs_to :menu_item
after_create :decrement_order
def decrement_order
self.food_product.update_attribute("amount_ordered_", (food_product.amount_ordered_ - 1))
end
end
I have a User table and a Booking Table that is linked by a create_join_table what holds the user id and booking ids. When a user books a room, i need the id of both the user and new booking to go into that. I am getting the error above and im not sure why.
I have looked online and saw something similar, their class names were plural however I don't think I have that.
booking.rb
class Booking < ApplicationRecord
enum room_type: ["Basic Room", "Deluxe Room", "Super-Deluxe Room", "Piton Suite"]
has_many :join_tables
has_many :users, through: :join_tables
end
user.rb
class User < ApplicationRecord
has_secure_password
validates :email, format: {with: URI::MailTo::EMAIL_REGEXP}, presence: true, uniqueness: true
has_many :join_tables
has_many :bookings, through: :join_tables
end
join_table.rb
class JoinTable < ApplicationRecord
belongs_to :users
belongs_to :bookings
end
bookings_controller.rb
def create
#booking = Booking.create(booking_params)
current_user.bookings << #booking ##Where the error happens
db/schema
ActiveRecord::Schema.define(version: 2019_12_13_181019) do
create_table "bookings", force: :cascade do |t|
t.integer "room_type"
t.date "check_in"
t.date "check_out"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "join_tables", force: :cascade do |t|
t.integer "users_id"
t.integer "bookings_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["bookings_id"], name: "index_join_tables_on_bookings_id"
t.index ["users_id"], name: "index_join_tables_on_users_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
I have just tried to reproduce your problem and I have a similar exception
irb(main):003:0> User.first.bookings
NameError (uninitialized constant User::Bookings)
but, when I change
belongs_to :users
belongs_to :bookings
to
belongs_to :user
belongs_to :booking
in app/models/join_table.rb everything works as expected.
This is how I created the JoinTable model
$ rails generate model JoinTable
class CreateJoinTables < ActiveRecord::Migration[6.0]
def change
create_table :join_tables do |t|
t.references :user
t.references :booking
t.timestamps
end
end
end
As you can see in the belongs_to docs, it is used in the singular form most of the time.
In my online shop I have tables Product and Size, also I think I need to add a table Restocking
Instead of updating a product, I guess It's better to have a Restocking table then I could track the dates where I added any new sizes, quantity, and why not the new prices (buying and selling)... and create stats...
Do you this it is correct?
Once a Restocking is created, the corresponding Product is updated with new quantity and price?
Well,
So it started this way:
#Product
has_many :sizes
accepts_nested_attributes_for :sizes, reject_if: :all_blank, allow_destroy: true
#Size
belongs_to :product
The Restocking table needs to have sizes attributes (like product)
I believe that I have to use polymorphic associations, but how I am supposed to update my schema , what should I add, remove?
So since I added the Restocking model, my models look like this:
#Product
has_many :sizes, inverse_of: :product, dependent: :destroy, as: :sizeable
has_many :restockings
accepts_nested_attributes_for :sizes, reject_if: :all_blank, allow_destroy: true
#Restocking
has_many :sizes, as: :sizeable
belongs_to :product
accepts_nested_attributes_for :sizes, reject_if: :all_blank, allow_destroy: true
#Size
belongs_to :product
belongs_to :restocking
belongs_to :sizeable, polymorphic: true, class_name: "Size"
schema.rb
create_table "sizes", force: :cascade do |t|
t.string "size_name"
t.integer "quantity"
t.bigint "product_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "quantity_stock"
t.index ["product_id"], name: "index_sizes_on_product_id"
end
create_table "restockings", force: :cascade do |t|
t.bigint "product_id"
t.bigint "sizeable_id"
t.decimal "price", precision: 10, scale: 2
t.decimal "buying_price", precision: 10, scale: 2
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["product_id"], name: "index_restockings_on_product_id"
t.index ["sizeable_id"], name: "index_restockings_on_sizeable_id"
end
create_table "products", force: :cascade do |t|
t.string "title", limit: 150, null: false
t.text "description"
t.bigint "category_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "color"
t.integer "user_id"
t.json "attachments"
t.string "brand"
t.string "ref"
t.decimal "price"
t.decimal "buying_price", precision: 10, scale: 2
t.index ["category_id"], name: "index_products_on_category_id"
end
At this point I have several errors, like
in ProductsController
def new
#product = Product.new
#product.sizes.build
end
error:
ActiveModel::UnknownAttributeError at /admin/products/new
unknown attribute 'sizeable_id' for Size.
Can you light me on the migrations I have to change?
Suggestions are welcome
You're almost there, to use polymorphic inside your Size model, you have to change the size resource, and add two attributes to the resource: sizeable_id and sizeable_type.
The sizeable_type is a string, indicates the class of the parent element, in your case, can be Product or Restocking, and sizeable_id indicates the element_id to find the parent element, your relations are correct, but you must add this elements to your Size, see the following:
One exemple of a migration to your case:
class AddSizeableToSize < ActiveRecord::Migration
def change
add_reference :sizes, :sizeable, polymorphic: true, index: true
end
end
On your Size model:
# app/models/size.rb
class Size < ActiveRecord::Base
belongs_to :sizeable, polymorphic: true
end
In your Product or Restocking model:
has_many :sizes, as: :sizeable
This is just a simple way to make your case works! If you want to know more about rails associations and polymorphism, can take a look in this link.
I have been strugling on this issue for 4 days and I am wondering whether I am not facing an ActiveRecord bug? I am trying to link a User model to a Callout model.
user.rb
class User < ActiveRecord::Base
has_many :callouts_users
has_many :callouts, through: :callouts_users
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
has_many :posts, inverse_of: :creator
has_many :callouts, as: :calloutable
has_many :profiles, as: :profileable, :validate => true
validates :name, presence: true
validates_uniqueness_of :name, :case_sensitive => false, :message => "This name has already been taken"
end
callout.rb
class Callout < ActiveRecord::Base
has_many :callouts_users
has_many :users, through: :callouts_users
belongs_to :conversation
belongs_to :calloutable, polymorphic: true, class_name: "::Callout", :validate => true
validates :conversation, presence: true
validates :calloutable, presence: true
validates_uniqueness_of :calloutable_id, :scope => [:user_id, :conversation_id, :calloutable_type]
end
user_callout.rb
class UserCallout < ActiveRecord::Base
belongs_to :user
belongs_to :callout
validates :type, presence: true,
validates :type, :inclusion=> { :in => ["up", "down"] }
end
My migrations are as follows:
..._create_callouts_users.rb
class CreateCalloutsUsers < ActiveRecord::Migration
def change
create_table :callouts_users do |t|
t.timestamps null: false
end
end
end
..._add_callout_to_callouts_users.rb
class AddCalloutToCalloutsUsers < ActiveRecord::Migration
def change
add_reference :callouts_users, :callout, index: true
add_foreign_key :callouts_users, :callouts
end
end
..._add_user_to_callouts_users.rb
class AddUserToCalloutsUsers < ActiveRecord::Migration
def change
add_reference :callouts_users, :user, index: true
add_foreign_key :callouts_users, :users
end
end
and when I try to do something like
#callout = #conversation.callouts.find_by(calloutable: #user)
if(#callout.nil?) #callout = Callout.new(conversation: #conversation, calloutable: #user)
#callout.users << current_user
#callout.save
I immediately have:
ActiveRecord::StatementInvalid in CalloutsController#create
SQLite3::SQLException: no such column: callouts.user_id: SELECT 1 AS one FROM "callouts" WHERE ("callouts"."calloutable_id" IS NULL AND "callouts"."user_id" IS NULL AND "callouts"."conversation_id" IS NULL AND "callouts"."calloutable_type" IS NULL) LIMIT 1
So as if ActiverRecords where looking for a "user_id" column on my callouts table while the user_id is only on the join table side...
I am doing something wrong on my model? Why is my has_many - trough association not recogognized?
Here is the SQL code generated:
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE (LOWER("users"."name") = LOWER('name10') AND "users"."id" != 10) LIMIT 1
Callout Exists (0.6ms) SELECT 1 AS one FROM "callouts" WHERE ("callouts"."calloutable_id" IS NULL AND "callouts"."user_id" IS NULL AND "callouts"."conversation_id" = 1 AND "callouts"."calloutable_type" IS NULL) LIMIT 1
SQLite3::SQLException: no such column: callouts.user_id: SELECT 1 AS one FROM "callouts" WHERE ("callouts"."calloutable_id" IS NULL AND "callouts"."user_id" IS NULL AND "callouts"."conversation_id" = 1 AND "callouts"."calloutable_type" IS NULL) LIMIT 1
(0.0ms) rollback transaction
Completed 500 Internal Server Error in 50ms
ActiveRecord::Schema.define(version: 20150720002524) do
create_table "callouts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "conversation_id"
t.integer "calloutable_id"
t.string "calloutable_type"
end
add_index "callouts", ["calloutable_type", "calloutable_id"], name: "index_callouts_on_calloutable_type_and_calloutable_id"
add_index "callouts", ["conversation_id"], name: "index_callouts_on_conversation_id"
create_table "callouts_users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "callout_id"
end
add_index "callouts_users", ["callout_id"], name: "index_callouts_users_on_callout_id"
add_index "callouts_users", ["user_id"], name: "index_callouts_users_on_user_id"
create_table "conversations", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "conversation_id"
t.integer "creator_id"
t.text "title"
t.text "content"
end
add_index "posts", ["conversation_id"], name: "index_posts_on_conversation_id"
add_index "posts", ["creator_id"], name: "index_posts_on_creator_id"
create_table "potential_users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "profiles", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "profileable_id"
t.string "profileable_type"
t.string "description"
end
add_index "profiles", ["description"], name: "index_profiles_on_description", unique: true
add_index "profiles", ["profileable_type", "profileable_id"], name: "index_profiles_on_profileable_type_and_profileable_id"
create_table "users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "name"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
===================================================>
I have been strugling on this issue for 4 days and I am wondering whether I am not facing an ActiveRecord bug? I am trying to link a User model to a Callout model.
user.rb
class User < ActiveRecord::Base
has_many :callouts_users
has_many :callouts, through: :callouts_users
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
has_many :posts, inverse_of: :creator
has_many :callouts, as: :calloutable
has_many :profiles, as: :profileable, :validate => true
validates :name, presence: true
validates_uniqueness_of :name, :case_sensitive => false, :message => "This name has already been taken"
end
callout.rb
class Callout < ActiveRecord::Base
has_many :callouts_users
has_many :users, through: :callouts_users
belongs_to :conversation
belongs_to :calloutable, polymorphic: true, class_name: "::Callout", :validate => true
validates :conversation, presence: true
validates :calloutable, presence: true
validates_uniqueness_of :calloutable_id, :scope => [:user_id, :conversation_id, :calloutable_type]
end
user_callout.rb
class UserCallout < ActiveRecord::Base
belongs_to :user
belongs_to :callout
validates :type, presence: true,
validates :type, :inclusion=> { :in => ["up", "down"] }
end
My migrations are as follows:
..._create_callouts_users.rb
class CreateCalloutsUsers < ActiveRecord::Migration
def change
create_table :callouts_users do |t|
t.timestamps null: false
end
end
end
..._add_callout_to_callouts_users.rb
class AddCalloutToCalloutsUsers < ActiveRecord::Migration
def change
add_reference :callouts_users, :callout, index: true
add_foreign_key :callouts_users, :callouts
end
end
..._add_user_to_callouts_users.rb
class AddUserToCalloutsUsers < ActiveRecord::Migration
def change
add_reference :callouts_users, :user, index: true
add_foreign_key :callouts_users, :users
end
end
and when I try to do something like
#callout = #conversation.callouts.find_by(calloutable: #user)
if(#callout.nil?) #callout = Callout.new(conversation: #conversation, calloutable: #user)
#callout.users << current_user
#callout.save
I immediately have:
ActiveRecord::StatementInvalid in CalloutsController#create
SQLite3::SQLException: no such column: callouts.user_id: SELECT 1 AS one FROM "callouts" WHERE ("callouts"."calloutable_id" IS NULL AND "callouts"."user_id" IS NULL AND "callouts"."conversation_id" IS NULL AND "callouts"."calloutable_type" IS NULL) LIMIT 1
So as if ActiverRecords where looking for a "user_id" column on my callouts table while the user_id is only on the join table side...
I am doing something wrong on my model? Why is my has_many - trough association not recogognized?
Here is the SQL code generated:
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE (LOWER("users"."name") = LOWER('name10') AND "users"."id" != 10) LIMIT 1
Callout Exists (0.6ms) SELECT 1 AS one FROM "callouts" WHERE ("callouts"."calloutable_id" IS NULL AND "callouts"."user_id" IS NULL AND "callouts"."conversation_id" = 1 AND "callouts"."calloutable_type" IS NULL) LIMIT 1
SQLite3::SQLException: no such column: callouts.user_id: SELECT 1 AS one FROM "callouts" WHERE ("callouts"."calloutable_id" IS NULL AND "callouts"."user_id" IS NULL AND "callouts"."conversation_id" = 1 AND "callouts"."calloutable_type" IS NULL) LIMIT 1
(0.0ms) rollback transaction
Completed 500 Internal Server Error in 50ms
ActiveRecord::Schema.define(version: 20150720002524) do
create_table "callouts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "conversation_id"
t.integer "calloutable_id"
t.string "calloutable_type"
end
add_index "callouts", ["calloutable_type", "calloutable_id"], name: "index_callouts_on_calloutable_type_and_calloutable_id"
add_index "callouts", ["conversation_id"], name: "index_callouts_on_conversation_id"
create_table "callouts_users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "callout_id"
end
add_index "callouts_users", ["callout_id"], name: "index_callouts_users_on_callout_id"
add_index "callouts_users", ["user_id"], name: "index_callouts_users_on_user_id"
create_table "conversations", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "conversation_id"
t.integer "creator_id"
t.text "title"
t.text "content"
end
add_index "posts", ["conversation_id"], name: "index_posts_on_conversation_id"
add_index "posts", ["creator_id"], name: "index_posts_on_creator_id"
create_table "potential_users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "profiles", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "profileable_id"
t.string "profileable_type"
t.string "description"
end
add_index "profiles", ["description"], name: "index_profiles_on_description", unique: true
add_index "profiles", ["profileable_type", "profileable_id"], name: "index_profiles_on_profileable_type_and_profileable_id"
create_table "users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "name"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
=============================> EDIT
Ok,
I have another clue. I think that the error lies in the User Model but I don't know how to write it.
To make short:
1.)
Different UserS can participate in differents Callouts. So User <=> Callout is a "has_many / through" relation. (not HABTM because I need to customize the Join Model). So I can write #callout.users
2.)
One Callout targets at one Calloutable (Calloutable are either User or PotentialUser). But a Calloutable may be targeted by different Callouts. So it is a belong_to / has_many Polymorphic relation. I can write #user.callouts...and also #callout.users...
But : #callout.users in situation 1) or 2) don't mean the same thing.
Here are the detailled models:
class Callout < ActiveRecord::Base
has_many :callouts_users
has_many :users, through: :callouts_users
belongs_to :calloutable, polymorphic: true, class_name: "::Callout", :validate => true
validates :calloutable, presence: true
validates_uniqueness_of :calloutable_id, :scope => [:user_id, :conversation_id, :calloutable_type]
belongs_to :conversation
validates :conversation, presence: true
end
class CalloutsUser < ActiveRecord::Base
belongs_to :user
belongs_to :callout
validates_uniqueness_of :user_id, :scope => [:callout_id]
end
class User < ActiveRecord::Base
has_many :callouts_users
has_many :callouts, through: :callouts_users
has_many :callouts, as: :calloutable
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
has_many :posts, inverse_of: :creator
has_many :profiles, as: :profileable, :validate => true
validates :name, presence: true
validates_uniqueness_of :name, :case_sensitive => false, :message => "This name has already been taken"
end
class PotentialUser < ActiveRecord::Base
has_many :callouts, as: :calloutable
has_one :profile, as: :profileable, :validate => true
validates :profile, presence: true
end
Any idea to rewrite the User Model part ? (the 2 has_many :callouts) I think I just need to make difference to rails between #user.callouts received by the user and #user.callouts made by the user...
I feel as though you've made this harder than it needs to be. Don't fight the framework, it's here to help!
First, for your join table, use the standard Rails naming convention: order the models alphabetically. If you roll back a bit, rather than create three migrations for one action, why not:
rails g migration callouts_users callout_id:integer user_id:integer
That coupled with has_and_belongs_to_many relations in your models and you should be good to go.
It is fixed !!
the problem was in fact that I had written :
has_many :callouts_users
has_many :callouts, through: :callouts_users
has_many :callouts, as: :calloutable
So I was defining has_many :callouts, twice. And of course, Rails didn't know how to understand #callout.users
With :
has_many :callouts_users
has_many :callouts, through: :callouts_users, source: "call", class_name: "Call"
has_many :callins, as: :callable, class_name: "Call"`
I works perfectely !
Thank you for your patience and comprehension for the neewbie I am... :-)