Rails 5 - Associated Models, with the child being used by multiple - ruby-on-rails

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

Related

How to use a Link table with Active Record query interface

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

Rails compound key to enforce uniqueness on the pair of values

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.

Ransack: Conditions on both a child, and its respective grandchild record

Don't be intimidated by the length. It's probably actually quite simple.I couldn't find an answer, though I tried looking everywhere. I hope this is a good enough challenge for you.
Here goes:
Panel.rb
has_many :status_dates
has_many :statuses, through: :status_dates
StatusDate.rb
belongs_to :status
belongs_to :panel
def self.ransackable_attributes(auth_object = nil)
%w( current ) + _ransackers.keys
end
Status.rb
has_many :status_dates
Here is the schema.
create_table "panels", force: :cascade do |t|
t.string "no"
t.integer "project_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "status_dates", force: :cascade do |t|
t.integer "status_id"
t.integer "panel_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.date "date"
t.boolean "current"
end
add_index "status_dates", ["panel_id"], name: "index_status_dates_on_panel_id"
add_index "status_dates", ["status_id"], name: "index_status_dates_on_status_id"
create_table "statuses", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
What do I want to do?
I want to identify a parent record based on two conditions which must both simultaneously exist exist on: (i) a child record and (ii) in turn, that child's, child record too (i.e. a grand child record).
What does that mean?
Suppose we have:
I want to find all panels, where there is a particular StatusDate record such that StatusDate.current = y, and its respective child, Status.name = x.
(“Current” is actually a boolean value on the StatusDate record.)
What is happening at the moment?
Here is my _condition_fields.html.erb partial:
What is the problem?
Right now, ransack applies those conditions across different records. But I want them applied directly to: (I) a child record and (ii) that particular child's child record as well.
Any idea how I can do this?
Assistance very much appreciated.
Did you try query following way:
Panel.ransack({StatusDate_current:0, StatusDate_Status_name: "Installed"})

How to make a has_and_belongs_to_many association when creating an instance of a model

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).

Rails - collection_select does not select a values from tables having translations

I am relatively new to RoR.
This works nicely:
<td><%= collection_select :competitions_members, :member_id, Member.all, :id, :first_name %></td>
This one picks no value (actually all such calls to tables with translations):
<td><%= collection_select :competitions_members, :tull_id, Tull.all, :id, :name %></td>
seeded data in competitions_members table
Member can be involved in many competition. Basically I have N:M relationship between members and competitions via competitions_members table.
Tull is a dictionary. Value to be set during the process of assigning members to a competition.
Data model classes:
class Member < ApplicationRecord
has_and_belongs_to_many :competitions
end
class Competition < ApplicationRecord
has_and_belongs_to_many :members
end
class CompetitionsMember < ApplicationRecord
end
Tull table has also translations in separate table.
class Tull < ApplicationRecord
translates :name
has_many :competitions_members
# separate different localizations of the record
def cache_key
super + '-' + Globalize.locale.to_s
end
end
Relevant schema.db excerpt
create_table "members", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "competitions", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "competitions_members", force: :cascade do |t|
t.integer "member_id"
t.integer "competition_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "tull_id"
t.index ["tull_id"], name: "index_competitions_members_on_tull_id"
end
create_table "tull_translations", force: :cascade do |t|
t.integer "tull_id", null: false
t.string "locale", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
t.index ["locale"], name: "index_tull_translations_on_locale"
t.index ["tull_id"], name: "index_tull_translations_on_tull_id"
end
create_table "tulls", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Any help apreciated. I just realized this might be connected with translated tables somehow.
class Tull < ApplicationRecord
has_many :translations
translates :name, :fallbacks_for_empty_translations => true
attr_accessible :translations_attributes
end
Try to execute below code in rails console:
Tull.first.translations - If this gives you translation records that means the associations are correct.
Now check at view side, how would you generate attributes for multilingual stuffs. I would suggest to use globalize_accessors.
Please send me the codebase.

Resources