N-M relationship unknow attribute error with create method - ruby-on-rails

Here are my migrations:
class CreateTests < ActiveRecord::Migration
def change
create_table :tests do |t|
t.string :value
t.timestamps
end
end
end
class CreateQuestions < ActiveRecord::Migration
def change
create_table :questions do |t|
t.string :title
t.timestamps
end
end
end
class CreateQuestionsTests < ActiveRecord::Migration
def change
create_table :questions_tests do |t|
t.integer :test_id
t.integer :question_id
t.timestamps
end
end
end
Now in the rails console I created a test object and a question object
test = Test.create(value: "10")
question = Question.create(title: "blablabla")
If now I do test.questions.create(question_id: question.id) I get the following error:
ActiveRecord::UnknownAttributeError: unknown attribute: question_id
How is that?

If you are using has_and_belongs_to_many relationship, you must to have relation table without id and stamps
class CreateQuestionsTests < ActiveRecord::Migration
def change
create_table :questions_tests, :id => false do |t|
t.integer :test_id
t.integer :question_id
end
end
end

I guess that you want to do a Rich association here, if so you should declare relations in your models like this :
Test.rb
class Test < ActiveRecord::Base
has_many :questions_tests
has_many :questions, :through => :questions_tests # here you tell rails that your Test model has many questions if you go through questions_tests
end
Question.rb
class Question < ActiveRecord::Base
has_many :questions_tests
has_many :tests, :through => :questions_tests # here you tell rails that your Question model has many tests if you go through questions_tests
end
QuestionTest.rb
class QuestionTest < ActiveRecord::Base
belongs_to :test
belongs_to :question
end
with this you can traverse the associations table (questions_tests) directly like you want : test.questions.create(question_id: question.id), and you have also this possibility :
test = Test.create(value: "10")
question = Question.create(title: "blablabla")
test.questions_tests << question # or question.questions_tests << test

Related

Active Record - Rails 6, Having a model that two unrelated models use

Lets say Im building a watch classifier system in rails 6. I have a model called material, material is use by both the watch body and bracelet.
the model would be something like this:
Material:
description - text (gold, platinum, steel, silver etc)
Bracelet:
style - text
Material - has_many references (could be silver and rose gold etc)
clasp - text
etc
Watch
brand - text
Material - has_many references (case could be gold & white Gold etc)
etc
As you can see both Bracelet and Watch are dependent on material in one to many, but material doesn't care or need to know about Watch or Bracelet so belongs_to: doesn't suit, nor does a polymorphic association
How do I model this in rails 6? and what would the migrations look like?
Thanks
Something like this:
class CreateMaterials < ActiveRecord::Migration[6.0]
def change
create_table :materials do |t|
t.integer :description, default: 0
t.timestamps
end
end
end
class Material < ApplicationRecord
enum description: %i[gold, platinum, steel, silver] # etc
has_and_belongs_to_many :watches
has_and_belongs_to_many :bracelets
end
class CreateBracelets < ActiveRecord::Migration[6.0]
def change
create_table :bracelets do |t|
t.text :style
t.text :clasp
# etc
t.timestamps
end
end
end
class Bracelet < ApplicationRecord
has_and_belongs_to_many :materials
end
class CreateWatches < ActiveRecord::Migration[6.0]
def change
create_table :watches do |t|
t.text :brand
# etc
t.timestamps
end
end
end
class Watch < ApplicationRecord
has_and_belongs_to_many :materials
end
class CreateMaterialsBracelets < ActiveRecord::Migration[6.0]
def change
create_table :materials_bracelets, id: false do |t|
t.belongs_to :material, foreign_key: true
t.belongs_to :bracelet, foreign_key: true
end
end
end
class CreateMaterialsWatches < ActiveRecord::Migration[6.0]
def change
create_table :materials_watches, id: false do |t|
t.belongs_to :material, foreign_key: true
t.belongs_to :watch, foreign_key: true
end
end
end
How do you like this decision? If something is wrong, then say it.

Multiple associations to the same model in Rails

Let's say I have a model Dogs and each dog has exactly 2 Cat "enemies", enemy1 and enemy2, how do I write the migration file such that I can call dog.enemy1 to retrieve the first enemy and dog.enemy2 to retrieve the second enemy?
I tried this:
create_table :dog do |t|
t.string :name
t.timestamps null: false
end
add_index :dog, :name
add_foreign_key :dogs, :cats, column: :enemy1_id
add_foreign_key :dogs, :cats, column: :enemy2_id
end
I also tried it with the t.references method but could not get it to work. Been working on this problem for hours. and it works fine in development but not on Heroku Postgres.
The error i get is
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR:column "enemy1_id" referenced in foreign key constraint does not exist.
Any ideas?
Migration:
create_table :dog do |t|
t.string :name
t.integer :enemy1_id
t.integer :enemy2_id
t.timestamps null: false
end
Dog Model:
class Dog < ActiveRecord::Base
has_one :enemy1, class: 'Cat', foreign_key: :enemy1_id
has_one :enemy2, class: 'Cat', foreign_key: :enemy2_id
end
There is not a 'has_two' association in rails. So you should settle with has_many association.
class Dog < ActiveRecord::Base
has_many :cats, limit: 2
scope :enemy1, Proc.new { |object| object.cats.first }
scope :enemy2, Proc.new { |object| object.cats.last }
end
in class cat
class Cat < ActiveRecord::Base
belongs_to :dog
end
Now the migration for create cats should have
t.references :dog
You need not have foreign fields in your dog model. This should solve your problems.
The Rails Way should not be to do this in a migration. Migrations, in my opinion, is a tool to define the structure your data would live in.
The Rails Way would suggest you have a has_many association on the Cat class for Dogs
class Dog < ApplicationRecord
# ... other code ...
has_many :enemies, class_name: Cat
# ... other code ...
end
You'd also have to define the belongs to association in Cat
class Cat < ApplicationRecord
# ... other code ...
belongs_to :dog
# ... other code ...
end
Note that the cats table should be defined to have a reference to the dogs table. So your migration for the cats table should read something like
class CreateCats < ActiveRecord::Migration[5.x]
create_table :cats do |t|
# ... other code ...
t.references :dog
# ... other code ...
end
end
With these setup, you should then define enemy_one and enemy_two in your Dog class
class Dog < ApplicationRecord
# ... other code ...
def enemy_one
enemies.first
end
def enemy_two
enemies.second
end
# ... other code ...
end
To make things even stricter, you may decide to add a validation (on create) which checks that only two Cats are ever created as enemies per Dog. Moreso, you could hide the creating button (or general access) when a Dog has reached its limit for enemies. This, I leave to your discretion.
p.s: All these constrictions could also be done in the database layer. But the application layer is here to abstract all that out.
I just solved it independently, 2 seconds after the first answer came in..
Finally.
class CreateDogs < ActiveRecord::Migration
def change
create_table :dogs do |t|
t.string :name
t.references :enemy1, index: true
t.references :enemy2, index: true
t.timestamps null: false
end
add_index :dogs, :name
add_foreign_key :dogs, :cats, column: :enemy1_id
add_foreign_key :dogs, :cats, column: :enemy2_id
end

Rails: incrementing attribute from a model, upon creation of an instance from another model

Our Rails app works with the following models:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
end
And here are our migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
...
t.integer :total_calendar_count
t.integer :owned_calendar_count
t.timestamps null: false
end
end
end
class CreateAdministrations < ActiveRecord::Migration
def change
create_table :administrations do |t|
t.references :user, index: true, foreign_key: true
t.references :calendar, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end
class CreateCalendars < ActiveRecord::Migration
def change
create_table :calendars do |t|
t.string :name
t.timestamps null: false
end
end
end
When a new #calendar is created, we need to increment :total_calendar_count and :owner_calendar_count by one in the User table.
We tried this in the CalendarsController:
class CalendarsController < ApplicationController
def create
#calendar = current_user.calendars.create(calendar_params)
current_user.total_calendar_count += 1
current_user.owned_calendar_count += 1
current_user.administrations.find_by(calendar_id: #calendar.id).update(role: 'Creator')
...
end
But it does not seem to update :total_calendar_count and :owner_calendar_count by one in the User table.
Are we missing a step here? Should we use an update action instead?
The actual problem in your code is that you don't then save the user.
So you update the counter... but this changes it on the local instance... and then after the controller action is done the change you made just disappears.
if you wanted to keep your code the way it is, you could do:
current_user.save
at the end.
but I'd advise you to look into the counter_cache, because it's the Rails way.
Also I'll point out that you haven't checked that the calendar successfully got created, before incrementing that counter... it's possible that it could fail a validation and not really have been created... you need to check for that first.
I have a best idea to solve your problems is as below....
Create a method that will call on the creating of calendar with the callbacks of model like as below...
Add the below inside the calendar model just after the validation and ORM relations
after_create :increment_counter
def increment_counter
calendar_user = self.user
calendar_user.update(:total_calendar_count += 1, :owned_calendar_count += 1 )
end
With the above code you don't need to do anything. It will increment the counter of calendar on every new entry of calendar.

Render children of parent rails 4.0

I have a Contract model that has_many Task_Orders. I am trying to render a view where if I click "Show" for Contract line item, it will display a list of Task_Orders that belong to that Contract.
Here is my Contract schema:
create_table "contracts", force: true do |t|
t.string "contractId"
t.string "contractName"
Here is my Task Order schema:
create_table "task_orders", force: true do |t|
t.integer "contract_Id", limit: 255
t.string "task_orderId"
t.string "task_orderName"
Here is my Contract model:
class Contract < ActiveRecord::Base
has_many :task_orders
end
Here is my Task Order model:
class TaskOrder < ActiveRecord::Base
belongs_to :contract
end
I am not entirely sure how to work with the controller and view to make it happen.... please help. I am using Rails 4.0
Thank you.
foreign_key
Firstly, you need to ensure your foreign_keys are assigned for your associations:
#app/models/task_order.rb
Class TaskOrder < ActiveRecord::Base
belongs_to :contract, primary_key: "contractID", foreign_key: "contract_Id"
end
#app/models/contract.rb
Class Contract < ActiveRecord::Base
has_many :task_orders, primary_key: "contractID", foreign_key: "contract_Id"
end
--
Controller
This should allow you to call the required data from your controller:
#app/controllers/contracts_controller.rb
Class ContractsController < ApplicationController
def show
#contract = Contract.find params[:id]
end
end
#app/views/contracts/show.html.erb
<% for order in #contract.task_orders do %>
<%= order.id %>
<% end %>

Rails model multiple has_many :through relationships output to json

I have the following data models and would like to render a json hash that includes information from each model. For example, client.id, client.name_first, client, name_last, every workout description for each client and each exercise description for each workout.
class Client < ActiveRecord::Base
belongs_to :account
belongs_to :trainer
has_many :programs
has_many :workouts, :through => :programs
end
class Workout < ActiveRecord::Base
has_many :programs
has_many :clients, :through => :programs
has_many :routines
has_many :exercises, :through => :routines
end
class Exercise < ActiveRecord::Base
has_many :routines
has_many :workouts, :through => :routines
end
My database migrations:
class CreateClients < ActiveRecord::Migration
def change
create_table :clients do |t|
t.integer :account_id
t.integer :trainer_id
t.string :name_first
t.string :name_last
t.string :phone
t.timestamps
end
end
end
class CreateWorkouts < ActiveRecord::Migration
def change
create_table :workouts do |t|
t.string :title
t.string :description
t.integer :trainer_id
t.timestamps
end
end
end
class CreateExercises < ActiveRecord::Migration
def change
create_table :exercises do |t|
t.string :title
t.string :description
t.string :media
t.timestamps
end
end
end
I am able to return the workouts for a particular client:
#client = Client.find(params[:id])
clients_workouts = #client.workouts.select('workouts.*,programs.client_id').group_by(&:client_id)
render json: clients_workouts
And I am able to return the exercises for a particular workout:
#workout = Workout.find(params[:id])
exercises_workouts = #workout.exercises.select('exercises.*, routines.workout_id').group_by(&:workout_id)
render json: exercises_workouts
However, I do not know how to return the data with information from all three tables (Client, Workout, Exercise) included (joined through Programs and Routines). Is this possible? And how is it done?
First, I'm not really sure what's happening in your query:
clients_workouts = #client.workouts.select('workouts.*,programs.client_id').group_by(&:client_id)
Is this not sufficient?
#client.workouts
Now, on to the answer... assuming I'm still following:
ActiveRecord offers a .to_json method, which is what's being implicitly called here. The explicit version would be e.g.
render json: clients_workouts.to_json
Knowing that, you can look up to_json in the api (here's some good documentation even though it shows as deprecated: http://apidock.com/rails/ActiveRecord/Serialization/to_json). But, basically, the answer is to start with the root object -- the client I believe -- and build the included objects and attributes/methods from there in the options hash.
render json: #client.to_json(include: { workouts: { include: :exercises } })
You can customize which attributes or methods are included from each related model if needed, just dig into the documentation a little. Have fun!
Very possible and their are different ways to optain this.
One, without any 3rd party library is to use includes, just as if you were solving an n+1 problem or…
Use a much cooler approach and use active model serializers
Active Model Serializers

Resources