I am trying to build a toy application and ran across an issue I cannot seem to solve. How do I enforce that a pair of values are unique in a table?
suppose the following schema:
create_table "courses", force: :cascade do |t|
t.string "title"
t.text "description"
t.string "number"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "status", default: 0
end
create_table "professors", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "status", default: 0
end
create_table "sections", force: :cascade do |t|
t.integer "number"
t.integer "max_enrollment"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "professor_id"
t.integer "course_id"
t.string "room"
t.index ["course_id"], name: "index_sections_on_course_id"
t.index ["professor_id"], name: "index_sections_on_professor_id"
end
and I wanted to create a uniqueness constraint in the sections table that the professor_id paired with course_id must be unique. the only thing I have found in my digging is that you can use the validates keyword in the model to enforce the uniqueness of a single field... I also saw that there is a validates_with keyword but I cannot find any way of writing a validator to do what I'm looking for. any help would be greatly appreciated.
Add a unique constraint in your database (Pun in a migration):
add_index :sections, [:professor_id, :course_id], unique: true
Now also put a validation constraint in your Section model:
validates_uniqueness_of :professor_id, scope: :course_id
Now your professor_id will be uniquely validated in the scope of course_id. Also there will be a unique constraint in your database table.
Related
I am still relatively new to Active Record and have a (hopefully simple) question.
I have four tables Recipes(Name of recipe), Food(lettuce, Pepper), Units(oz, tbsp), and Ingredients(Id's of the other tables and the numeric quantity).
What I'd like to do is something like this Recipes.Ingredients and get "Peppered Lettuce, 5 tbsp pepper, 10 oz Lettuce".
How would I accomplishes that with the following schema. And if not possible with this schema what should I build in its place.
The Schema looks like this:
create_table "food", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "ingredients", force: :cascade do |t|
t.bigint "recipes_id"
t.bigint "units_id"
t.bigint "food_id"
t.decimal "quantity"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["food_id"], name: "index_ingredients_on_food_id"
t.index ["recipes_id"], name: "index_ingredients_on_recipes_id"
t.index ["units_id"], name: "index_ingredients_on_units_id"
end
create_table "recipes", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
end
create_table "units", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
so what I understand from your question is you want to get results like:
Peppered Lettuce, 5 tbsp pepper, 10 oz Lettuce in which Peppered Lettuce is Recipe name, and Pepper and Lettuce are Food items with numeric quantities of ingredients.
You don't need 4 tables to obtain this result. You only need 3. Food, Recipe and an in-between table for their many-to-many association.
Recipe can have multiple Food items in it and 1 Food item can be a part of multiple Recipe objects. So Food and Recipe model will have a many-to-many association. And for this kind of association, you need another table. You can name it Foods_Recipes or simply Ingredients.
Your Ingredient model will then have a food_id,recipe_id,numeric_quantity and unit_type
The Schema would look like this:
create_table "foods", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "ingredients", force: :cascade do |t|
t.bigint "recipe_id"
t.bigint "food_id"
t.decimal "quantity"
t.string "unit_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["food_id"], name: "index_ingredients_on_food_id"
t.index ["recipe_id"], name: "index_ingredients_on_recipe_id"
end
create_table "recipes", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
end
Your models would look like this:
Food model:
class Recipe < ApplicationRecord
has_many :ingredients
has_many :foods,through: :ingredients
end
Ingredient model:
class Ingredient < ApplicationRecord
belongs_to :recipe
belongs_to :food
end
Food model:
class Food < ApplicationRecord
has_many :ingredients
has_many :recipes,through: :ingredients
end
Having an issue with the proper order of associations.
I have a Character a CharClass and all CharClasses have a set of available Perks to them. a Character can choose which Perks to take and which to ignore.
I will manually populate the CharClass table and Perks table with the set-in-stone choices.
Thanks to Benjamin's comment below I have modified my schema to look as this(Minus non-related stuff):
ActiveRecord::Schema.define(version: 20180408124936) do
create_table "char_classes", force: :cascade do |t|
t.string "class_name"
t.string "class_perks"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "character_perks", force: :cascade do |t|
t.integer "character_id"
t.integer "class_perk_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["character_id"], name: "index_character_perks_on_character_id"
t.index ["class_perk_id"], name: "index_character_perks_on_class_perk_id"
end
create_table "characters", force: :cascade do |t|
t.string "character_name"
t.integer "character_level"
t.integer "character_experience"
t.integer "character_gold"
t.integer "campaign_id"
t.integer "char_class_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["campaign_id"], name: "index_characters_on_campaign_id"
t.index ["char_class_id"], name: "index_characters_on_char_class_id"
end
create_table "class_perks", force: :cascade do |t|
t.string "perk_description"
t.integer "currently_has"
t.integer "maximum_available"
t.integer "perk_id"
t.integer "char_class_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["char_class_id"], name: "index_class_perks_on_char_class_id"
t.index ["perk_id"], name: "index_class_perks_on_perk_id"
end
create_table "perks", force: :cascade do |t|
t.string "perk_text"
t.integer "perk_max_count"
t.integer "perk_current_count", default: 0
t.integer "char_class_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["char_class_id"], name: "index_perks_on_char_class_id"
end
end
This has resolved all data errors, but has a bit of redundancy. Due to the SO Flow of One Answer per Question, this did handle my primary ask for the original need. I will make an additional question for the follow-up.
My thoughts on how you might want to set up your tables, but I don't think there's an obvious best answer here. It's pretty much what you were going after but there are semantics I'll cover.
CharClass
has_many :class_perks
has_many :characters
ClassPerk
belongs_to :char_class # Use intermediate table if you want has_many/has_many
has_many :character_perks # between CharClass and ClassPerk for perk overlap
Character
belongs_to :char_class
has_many :class_perks, :through => char_class
has_many :character_perks
CharacterPerk
belongs_to :character
belongs_to :class_perk
This set up allows you to check which perks are available to a character based on class, and still keep the character's progress separate from the class perks.
Some nitpicky things:
belongs_to means that this table/model is the one that contains the foreign id to the relevant model. I think it makes more sense for the character to have the reference to the class.
A couple of the errors you're running into don't seem directly related to the question, but could be side effects of weirdly set up tables. I don't mind going through them here but some people might argue they should be separate posts. It might be more beneficial to show more of your schema rather than the migration.
Regarding the save error, if you're using strong params, remember to permit char_class_id and that the input name matches the attribute on the table
I have a column/foreign key, resolver_id, that I want to be able to have null values (ie: Rails Migration to make a column null => true). Let's say I have the following line in my migration:
def
change_column_null :bugs, :resolver_id, true
end
However, after running a successful migration (ie, generate the migration and run rails db:migrate), the schema remains unchanged, besides the version number:
t.integer "resolver_id"
whereas I am expecting:
t.integer "resolver_id" , null: true
Is there something I'm missing?
I've also tried using just change_column like so:
change_column :bugs, :resolver_id, :integer, null: true
However, this is still not reflected in the schema. The rails g migration and db:migrate work just fine, and the version number in the schema matches the latest migration.
For reference, here is my schema:
ActiveRecord::Schema.define(version: 20170502203934) do
create_table "bugs", force: :cascade do |t|
t.string "name"
t.text "error_msg"
t.text "description"
t.text "causes"
t.boolean "resolved"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "resolver_id"
t.index ["resolver_id"], name: "index_bugs_on_resolver_id"
t.index ["user_id"], name: "index_bugs_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "username"
t.string "first_name"
t.string "last_name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["username"], name: "index_users_on_username", unique: true
end
end
If relevant, the resolver_id foreign key is a reference a User model, ie:
class Bug < ApplicationRecord
# Associations
belongs_to :user
belongs_to :resolver, class_name: 'User'
end
null: true is the default behavior. You will never see it in your schema, you will see either null: false or nothing.
c = Course.create name:"Math1", ??
I'm trying to figure out how to associate this course to the existing students with has_and_belongs_to_many. Below is my schema with the associations. I'm having trouble creating instances of each and putting in their specific associations.
ActiveRecord::Schema.define(version: 20170127225124) do
create_table "courses", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "students", force: :cascade do |t|
t.string "username"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "students_courses", id: false, force: :cascade do |t|
t.integer "students_id"
t.integer "courses_id"
t.index ["courses_id"], name: "index_students_courses_on_courses_id"
t.index ["students_id"], name: "index_students_courses_on_students_id"
end
end
Well.. in a has_and_belongs_to_many association you don't need to worry about the middle table. Here you can find the methods added to the model by this type of association.
So, one way you can do this is by passing an array of the students ids, like:
student = Student.create username: "John"
c = Course.create name: "Math1", student_ids: [student.id]
You could also use the create method:
c.students.create({username: "John"})
Also, be careful with your join table. By default rails expect it to be in the alphabetical order, so would be courses_students instead. For that purpose I usually prefer to use the create_join_table command(reference here).
I have two models. An Events model and an EventOption. The Events will have_many :event_options.
My issue is that when I try to do a migration to add_foreign key :event_options, :events so that I can link them up, I get the following error:
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column "event_id" referenced in foreign key constraint does not exist
: ALTER TABLE "event_options" ADD CONSTRAINT "fk_rails_3995702fad"
FOREIGN KEY ("event_id")
REFERENCES "events" ("id")
Here's my schema.rb:
ActiveRecord::Schema.define(version: 20160806001743) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "event_options", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.float "price"
t.text "description"
t.string "name"
end
create_table "events", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
t.boolean "active", default: true
end
create_table "users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email", null: false
t.string "encrypted_password", limit: 128, null: false
t.string "confirmation_token", limit: 128
t.string "remember_token", limit: 128, null: false
t.index ["email"], name: "index_users_on_email", using: :btree
t.index ["remember_token"], name: "index_users_on_remember_token", using: :btree
end
end
I know there are :id columns that work because I can play with them in the console. I know I'm missing something here to get the Foreign Keys working for the app, but for the life of me, I don't know what.
Wait, you mean the foreign key option for the has_many, that isn't what add_foreign_key does confusingly. It adds a foreign key constraint.
So in your migration you need to do add_column or add_reference first
add_reference :event_options, :event, index: true
add_foreign_key :event_options, :events