How to set unique index for uniqueness validation table? - ruby-on-rails

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

Related

How to derement using has_many :through Ruby/Rails

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

Associations in Rails for a basic Books app?

I am making a basic app for the growing incel online community linking them to various pieces of literature they could find useful, helpful and guiding. I am having difficulty creating proper associations.
I have several models:
User: user.rb, a model of a user to view, comment and like books.
like: like.rb, like model to assign likes to books.
comment: comment.rb, comment model to comment on book models (via a user).
book: book.rb, model for books, will route/view pdf's to host server.
bookshares: book_share.rb, will be a join table linking users to likes to comments to books and so on and vice versa.
godmodel: hypothetical model not yet implemented to link together everything in an all encompassing manner.
So, I want users to be able to be create with a username and be able to view, like and comment books on the 'website' that will eventually be migrated over to a android app. Here is my abysmal code:
class BookShare < ApplicationRecord
validates :book_id, presence: true, uniqueness: true
validates :viewer_id, presence: true, uniqueness: true
belongs_to :user
belongs_to :book
belongs_to :comment
end
class Book < ApplicationRecord
validates :title, presence: true
validates :author, presence: true
validates :user_id, presence: true
validates :isbn, presence: true
validates :title, presence: true
has_many :book_shares
has_many :users, through: :book_shares
has_many :likes, through: :book_shares
has_many :comments, through: :book_shares
end
class Comment < ApplicationRecord
validates :user_id, presence: true, uniqueness: true
validates :book_id, presence: true, uniqueness: true
validates :title, presence: true
has_many :book_shares
has_many :books, through: :book_shares
end
class GodModel < ApplicationRecord
has_many :books
has_many :users
has_many :comments
#has_many :likes
has_many :topics
has_many :book_shares
end
class Like < ApplicationRecord
# not fully implemented yet.
validates :user_id, presence: true
belongs_to :user
end
class User < ApplicationRecord
validates :username, presence: true, uniqueness: true
has_many :book_shares
has_many :comments, through: :book_shares
has_many :books, through: :book_shares
end
class Topic < ApplicationRecord
# not implemented yet
end
Here is my schema:
ActiveRecord::Schema[7.0].define(version: 2022_04_12_145402) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "book_shares", force: :cascade do |t|
t.integer "book_id", null: false
t.integer "viewer_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["book_id", "viewer_id"], name: "index_book_shares_on_book_id_and_viewer_id", unique: true
t.index ["book_id"], name: "index_book_shares_on_book_id"
t.index ["viewer_id"], name: "index_book_shares_on_viewer_id"
end
create_table "books", force: :cascade do |t|
t.string "title", null: false
t.string "author", null: false
t.integer "user_id", null: false
t.text "body_info"
t.integer "isbn", null: false
t.binary "photo"
t.binary "r_data"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_books_on_user_id"
end
create_table "comments", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "book_id", null: false
t.text "body_txt"
t.string "title"
t.binary "photo"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["book_id"], name: "index_comments_on_book_id"
t.index ["user_id"], name: "index_comments_on_user_id"
end
create_table "god_models", force: :cascade do |t|
t.string "title"
t.integer "user_id"
t.integer "comment_id"
t.integer "book_share_id"
t.integer "book_id"
t.integer "like"
t.integer "topic"
t.binary "data_x"
t.date "today"
t.binary "title2"
t.boolean "nullfy"
t.float "nums"
t.text "body"
t.datetime "create_at_"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["body"], name: "index_god_models_on_body"
t.index ["book_id"], name: "index_god_models_on_book_id"
t.index ["book_share_id"], name: "index_god_models_on_book_share_id"
t.index ["comment_id"], name: "index_god_models_on_comment_id"
t.index ["data_x"], name: "index_god_models_on_data_x"
t.index ["like"], name: "index_god_models_on_like"
t.index ["nullfy"], name: "index_god_models_on_nullfy"
t.index ["nums"], name: "index_god_models_on_nums"
t.index ["title2"], name: "index_god_models_on_title2"
t.index ["today"], name: "index_god_models_on_today"
t.index ["topic"], name: "index_god_models_on_topic"
t.index ["user_id"], name: "index_god_models_on_user_id"
end
create_table "likes", force: :cascade do |t|
t.integer "user_id"
t.string "likeable_type"
t.bigint "likeable_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["likeable_type", "likeable_id"], name: "index_likes_on_likeable"
t.index ["user_id", "likeable_type", "likeable_id"], name: "index_likes_on_user_id_and_likeable_type_and_likeable_id", unique: true
end
create_table "users", force: :cascade do |t|
t.string "username", null: false
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["username"], name: "index_users_on_username", unique: true
end
end
Example controller:
class UsersController < ApplicationController
def index
render plain: 'index'
end
def show
render plain: 'show'
end
def new
render plain: 'new'
end
def destroy
render plain: 'destroy'
end
def update
redner update: 'update'
end
private
def usershare_param
params.require(:user).permit(:username)
end
end
This is what I produced so far. I am able to create the model object, save them I think and populate their fields but I don't think my models are working with the given associations.
I tried using erd but it does not work. Given the use of my app is the models/associations correctly made? I want to have a user who can view/comment/like books of interest. A book can have many likes and comments, books can be viewed by many users, users can like and comment many books, topics will be implemented later to assort the books. The entire mechanism of liking/commenting/viewing via a user(s) will be implemented via a joins table called bookshares. I want to write my associations correctly before moving onto the view/routes part of the mini-protect.
With 4 tables, books, users, comments and likes. You can implement the given design.
a book can have many likers(users), a user can like many books, forms many-to-many relationship between users and books. Make likes a join table.
Similarly, a book has many comments, a user can write many comments. Make comments a join table between users and books with extra fields specific to comment.
# app/models/book.rb
has_many :likes, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :lovers, through: :likes, source: :user
has_many :commentors, through: :comments, source: :user
# app/models/user.rb
has_many :likes, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :fav_books, through: :likes, source: :book
# app/models/like.rb
belongs_to :book
belongs_to :user
# app/models/comment.rb
belongs_to :book
belongs_to :user
You can consult this guide to explore topics in more depth.

Rails join table name doesn't match migration or schema (PG::UndefinedTable: ERROR: relation does not exist)

In my Rails project with a Postgres database, I have a user and workspace model. They are associated by a many to many relationship (users_workspaces). If I open up my rails console and try to get all user workspaces with UserWorkspace.all, I get the following 'relation does not exist' error:
2.5.1 :001 > UserWorkspace.all
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR: relation "user_workspaces" does not exist)
LINE 1: SELECT "user_workspaces".* FROM "user_workspaces" LIMIT $1
^
: SELECT "user_workspaces".* FROM "user_workspaces" LIMIT $1
2.5.1 :002 >
I don't understand why it's looking for user_workspaces (user being singular) rather than users_workspaces (both names plural). I'll looked through my codebase to see if this is in fact set somewhere as user_workspaces, but can't find it. I've also run rails db:drop db:create db:migrate, but still no luck. Here are related files, but I'm not sure where is issue is originating from.
user model
class User < ApplicationRecord
has_secure_password
has_and_belongs_to_many :workspaces
validates_presence_of :username, :email, :password, :subscription_plan
validates_uniqueness_of :username, :email
validates_length_of :username, :within => 3..40
validates_length_of :password, :within => 8..100
end
workspace model
class Workspace < ApplicationRecord
has_and_belongs_to_many :users
validates_presence_of :name
validates_presence_of :admin_id
end
user_workspace model
class UserWorkspace < ApplicationRecord
belongs_to :user
belongs_to :workspace
validates_presence_of :user, :workspace
end
schema.rb
ActiveRecord::Schema.define(version: 2018_07_28_040836) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "users", force: :cascade do |t|
t.string "username", null: false
t.string "email", null: false
t.string "first_name"
t.string "last_name"
t.string "password_digest"
t.integer "subscription_plan", default: 0, null: false
t.integer "current_workspace"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["username"], name: "index_users_on_username", unique: true
end
create_table "users_workspaces", id: false, force: :cascade do |t|
t.bigint "user_id", null: false
t.bigint "workspace_id", null: false
t.index ["user_id", "workspace_id"], name: "index_users_workspaces_on_user_id_and_workspace_id"
t.index ["workspace_id", "user_id"], name: "index_users_workspaces_on_workspace_id_and_user_id"
end
create_table "workspaces", force: :cascade do |t|
t.string "name", null: false
t.text "description"
t.integer "admin_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
users migrations
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string :username, null: false, index: {unique: true}
t.string :email, null: false, unique: true
t.string :first_name
t.string :last_name
t.string :password_digest
t.integer :subscription_plan, null: false, default: 0
t.integer :current_workspace
t.timestamps
end
end
end
workspaces migration
class CreateWorkspaces < ActiveRecord::Migration[5.2]
def change
create_table :workspaces do |t|
t.string :name, null: false
t.text :description
t.integer :admin_id
t.timestamps
end
end
end
users_workspaces (join table) migration file
class CreateJoinTableUsersWorkspaces < ActiveRecord::Migration[5.2]
def change
create_join_table :users, :workspaces do |t|
t.index [:user_id, :workspace_id]
t.index [:workspace_id, :user_id]
end
end
end
Any and all help would be greatly appreciated. Thanks!
As mentioned in schema.rb table is created by the name users_workspaces and your class name is UserWorkspaces.
By default, rails try to infer the table name for a Model by its class name.
So, If classname is UserWorkspace then its corresponding table_name will be user_workspaces and not users_workspaces.
Now, You have two options either rename your model or somehow mention in your model that the table you want to use for this model.
Option-1
Rename Model
class UsersWorkspace < ApplicationRecord
belongs_to :user
belongs_to :workspace
validates_presence_of :user, :workspace
end
Option-2
Allow UserWorkspace model to point to users_workspaces table
class UserWorkspace < ApplicationRecord
self.table_name = 'users_workspaces'
belongs_to :user
belongs_to :workspace
validates_presence_of :user, :workspace
end
UPDATE
In addition to above in UserWorkspace/UsersWorkspace Model you don't need
validates_presence_of :user, :workspace
as since you are using rails 5.2, therefore, rails itself adds presence validation along with belongs_to association unless you have pass optional: true argument or you have declared it in the following way in application.rb
Rails.application.config.active_record.belongs_to_required_by_default = false

Rails 5.1.5: TypeError: can't cast ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array::Data

I'm getting a database ROLLBACK with this error in development when I try to update a Puzzle object's User object through the Rails console:
TypeError: can't cast ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array::Data
This only happens when I attempt to use update (or save after something like puzzle.user = some_user). Adding the initial owner commits to the database without issue.
Here are the models in the schema:
create_table "users", force: :cascade do |t|
t.string "username"
t.string "password_digest"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "location_id"
end
create_table "puzzles", force: :cascade do |t|
t.string "name"
t.integer "pieces"
t.integer "missing_pieces"
t.string "previous_owners", array: true
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
And here are the puzzle.rb and user.rb files so far:
class User < ApplicationRecord
validates :username, presence: true
validates :email, presence: true#, uniqueness: true
# use bcrypt for password security
has_secure_password
has_many :puzzles
has_many :reviews
belongs_to :location
end
class Puzzle < ApplicationRecord
validates :name, uniqueness: true
validates :pieces, presence: true, numericality: { only_integer: true }
belongs_to :user
has_many :puzzle_tags
has_many :tags, through: :puzzle_tags
has_many :reviews
delegate :location, to: :user
end
Any idea what could be causing the issue?
***Please note: I'm a newbie and using PostgreSQL for the first time. I specifically chose Postgres as my development database instead of SQLite3 because it allows for array data types. Thanks!
You have previous_owners set up as a string array, but you are pushing integers into it. ActiveRecord is good at casting strings to integers and vice versa normally, but as of Rails 5.1.5, that functionality doesn't work in array fields.
Try using a migration to change the field to an integer array. You'll need to do:
$ rails g migration change_previous_owners_to_integer_array
Then edit the resulting migration file like this:
def change
remove_column :puzzles, :previous_owners, :string, array: true
add_column :puzzles, :previous_owners, :integer, array: true
end

Has many belongs_to is not working Rails

This is a very noob question, I am starting with Rails. I have a class User that has many Rates and each rates belongs to a user.
Rate Class
class Rate < ActiveRecord::Base
belongs_to :user
end
User Class
class User < ActiveRecord::Base
include Authenticable
has_many :rates
validates :username, uniqueness: true, allow_blank: true, allow_nil: true
Migration of Rates
class CreateRates < ActiveRecord::Migration
def change
create_table :rates do |t|
t.string :points
t.timestamps null: false
end
end
end
Migration of User
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
## Database authenticatable
t.string :email, null: true, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.inet :current_sign_in_ip
t.inet :last_sign_in_ip
t.string :authentication_token, default: ""
## User attributes
t.string :username, default: ""
t.timestamps
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
add_index :users, :authentication_token, unique: true
end
end
When I create the database and run rake db:migrate, all entity are migrated ok but the table rate has not user_id
You need to add user_id column to rates table
Hard to say without seeing the migration files themselves, but you can use the add_reference function in your migrations to add a reference to a table:
class AddUserIdToRate < ActiveRecord::Migration
def change
unless column_exists? :rates, :user_id
add_reference :rates, :user, index: true
end
end
end
If you get an error, post it here and we can hopefully provide more information.
Edit
There's nothing in your migrations involving the creation of a user_id column on your rates table. Adding a migration with the above code should add the user_id foreign key.

Resources