Errors when using Rails has_many :through association - ruby-on-rails

My app consists of exercises that users add to workouts. Users can create exercises or select existing ones.
*** UPDATE ****
I've added a model per Ben's solutions.
I'm receiving errors as below when attempting to add exercises to workouts. Is my syntax wrong? I've attempted soltions like this:
w=Workout.last
e=Exercise.last
w.exercises.build(:exercise => e) # NameError: uninitialized constant Workout::ExercisesWorkout
w.exercises_workouts.create(:exericse_id => 1) #NameError: uninitialized constant Workout::ExercisesWorkout
I'm confused by the new methods attached from the association as well as "_" or not, camelCase, Pluralize, symbol..etc.
Rails seems to be looking for the class ExercisesWorkout yet I define "exercises_workouts" and/or ExercisesWorkouts.
Thanks.
I'm having trouble adding exercises to workouts from the rails console. 2 potential issues that I see:
I don't know the proper syntax to do this (build, create, find)
Application setup properly (join table, models,..etc.)
Please let me know where my error is and if there is a better structure / association to use.
Thank you.
Models:
class Exercise < ActiveRecord::Base
has_many :workouts, :through => :exercises_workouts
has_many :exercises_workouts
end
class Workout < ActiveRecord::Base
has_many :exercises, :through => :exercises_workouts
has_many :exercises_workouts
end
class ExercisesWorkouts < ActiveRecord::Base
belongs_to :exercise
belongs_to :workout
end
schema.db:
ActiveRecord::Schema.define(version: 20141129181911) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "exercises", force: true do |t|
t.string "name"
t.string "description"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "exercises_workouts", id: false, force: true do |t|
t.integer "exercise_id", null: false
t.integer "workout_id", null: false
end
create_table "workouts", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
end
ERROR:
w=Workout.new #create new workout
w.name = 'test' #name workout
w.save #save workout
e1=Exercise.new #create new exercise
e1.name='press' #name exercise
e1.save #save exercise
#I'm not sure of the syntax here... I've tried alot between create, build using symbols and finds...., this is just one example..
w.exercises.create(e1) #NameError: uninitialized constant Workout::ExercisesWorkout

You also need a model for the join table:
class ExercisesWorkouts < ActiveRecord::Base
belongs_to :exercise
belongs_to :workout
end
Here is answer that covers join tables in more detail, if you're interested:
What would the joining table called in this case in Rails 3.1?

Related

Referencing model from one model instance

This is my Schema.rb
Schema.rb
ActiveRecord::Schema.define(version: 20170617073406) do
create_table "actions", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", force: :cascade do |t|
t.string "post_id"
t.datetime "timestamp"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "user_id"
t.datetime "last_activity"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
These are my three model classes.
Action.rb
class Action < ApplicationRecord
belongs_to :post
has_many :users, :foreign_key => 'user_user_id'
end
Post.rb
class Post < ApplicationRecord
has_many :actions, :foreign_key => 'action_id'
end
User.rb
class User < ApplicationRecord
has_many :actions, :foreign_key => 'action_id'
end
I am trying to add an instance of Action object into Post model object.
post = Post.find(post_id=post_id)
current_action = post.actions.find_or_create_by(name: "like")
It gives me the following error:
SQLite3::SQLException: no such column: actions.action_id: SELECT "actions".* FROM "actions" WHERE "actions"."action_id" = ? AND "actions"."name" = ? LIMIT ?
I am new to Ruby on Rails and come from Django background. Please help me figure this out.
Action needs to be related to the user as many to many...
If you want a many-to-many association, you will need to use another table (i.e. a join model):
class Action < ApplicationRecord
belongs_to :post
has_many :user_actions
has_many :users, through: :user_actions
end
class User < ApplicationRecord
has_many :user_actions
has_many :actions, through: :user_actions
end
class UserAction < ApplicationRecord
belongs_to :action
belongs_to :user
end
The ID I want to store is longer than Integer can do.
You can specify your own id in the migration and avoid adding an extra action_id:
class CreateActions < ActiveRecord::Migration[5.1]
def change
create_table :actions, id: false do |t|
t.string :id, null: false
t.timestamps
end
add_index :actions, :id , unique: true
end
end
With the above setup, you don't need to specify any foreign_key in Post either, ActiveRecord will use defaults (i.e. action_id):
class Post < ApplicationRecord
has_many :actions
end
A note about associations and foreign keys (and why you got that error):
Whenever you create an association, the foreign_key must be created in the table with the belongs_to side, since ActiveRecord will look for that key there.
Even If you don't specify a belongs_to, a has_many reference to that table will still look for that foreign_key.
So, when you add
has_many :actions, :foreign_key => 'action_id'
you are telling ActiveRecord to look for action_id column in actions table, but that columns has not being created in actions table.
In the proposed solution, the foreign keys are on the join table model (i.e. UserActions), so you must create a migration to include them:
rails g migration CreateUserActions user:references action:references
Solution:
Run migrations in command line:
rails generate migration add_post_to_actions post:belongs_to
rake db:migrate
Then update:
class Action < ApplicationRecord
belongs_to :post
# ...
end
class Post < ApplicationRecord
self.primary_key = 'post_id'
has_many :actions
# ...
end
class User < ApplicationRecord
self.primary_key = 'user_id'
# ...
end
Explanation:
1st line would add post_id column to actions table, and then index it with foreign constraint
The above migrations are independent of the contents of your current model files. You can even delete your models/action.rb or models/user.rb, and you'll see that the migrations would still even run without problems, because migrations only "do" stuff on the actual current database. The migration files also do not even care about whatever is written in your schema.rb, although it will update that schema.rb each time you run a migration (after the database has already been migrated/updated).

If I specify a has_many :through association in Ruby on Rails, do I have to create a model for the 3rd table?

Let's say I have
class CreateAppointments < ActiveRecord::Migration
def change
create_table :physicians do |t|
t.string :name
t.timestamps null: false
end
create_table :patients do |t|
t.string :name
t.timestamps null: false
end
create_table :appointments do |t|
t.belongs_to :physician, index: true
t.belongs_to :patient, index: true
t.datetime :appointment_date
t.timestamps null: false
end
end
end
1- Do I have to define again the associations on the model file? which leads me to my next question....
2- Do I have to create a model for this third table of appointments or just run the migration and the Active Record will take care of updating it everytime a doctor and a patient updates? when will the insert to this third table will be triggered in this type of association?
There is alot of magic inside of active record, so i understand where you are coming from.
Yes, The migration will not add the proper associations to your ActiveRecord models. Migrations are there to make changes to the database.
Yes, if you did not generate this using rails g scaffold or model, then you will need to create an Appointment class that inherits from ActiveRecord::Base in order to work with it through the orm. Then put the proper associations on them (patients have many appointments, they would also have many doctors through appointments. vice versa for doctors.

rails admin has_many through relationship leading to errors

I am using rails_admin gem for admin interface.
I have a has_many through relationship which doesn't seem to work with rails admin.
class Company < ActiveRecord::Base
has_many :talent_infos, class_name: 'CompanyTalentInfo'
has_many :talents, through: :talent_infos
end
class CompanyTalentInfo < ActiveRecord::Base
belongs_to :company
belongs_to :talent
end
class Talent < ActiveRecord::Base
has_many :talent_infos, class_name: 'CompanyTalentInfo'
has_many :companies, through: :talent_infos
end
I get error every time I try to create a new company and my guess is that its the first time when rails_admin tries to check the relationships and it doesn't accept my current associations.
The error I get is this file gems/rails_admin-0.7.0/app/views/rails_admin/main/_form_filtering_multiselect.html.haml:21
21 controller.list_entries(config, :index, field.associated_collection_scope, false).map { |o| [o.send(field.associated _object_label_method), o.send(field.associated_primary_key)] }.sort_by {|a| [selected_ids.index(a[1]) || selected_ids.si ze, i+=1] }
I get this error
undefined method `klass' for nil:NilClass`
Can anyone help me with this association how can I fix it.
perhaps a late response, but could you compare the relevant part of your database schema with the following and let me know the difference? I believe the relations are setup correctly so that should be the problem.
create_table "companies", force: :cascade do |t|
t.string "name"
end
create_table "company_talent_infos", force: :cascade do |t|
t.string "metadata"
t.integer "company_id"
t.integer "talent_id"
end
create_table "talents", force: :cascade do |t|
t.string "name"
end

Forms and Routes for Has Many Through Relation in Rails4

I am a Rails newbie and trying to create a simple Rails4 application. I have User model which is generated by Devise GEM, Examination model and Participation models are both generated by scaffold generator.
These are my models:
class Examination < ActiveRecord::Base
has_many :participations
has_many :users, :through => :participations
end
class Participation < ActiveRecord::Base
belongs_to :user
belongs_to :examination
end
class User < ActiveRecord::Base
has_many :participations
has_many :examinations, :through => :participations
end
And Database Structure:
create_table "participations", force: true do |t|
t.integer "user_id"
t.integer "examination_id"
t.string "exam_language_preference"
....
end
create_table "users", force: true do |t|
t.string "email"
t.string "first_name"
....
end
create_table "examinations", force: true do |t|
t.string "name"
t.string "shortname"
t.datetime "exam_date"
end
Now, I would like to create the structure to make Users to be able to register to exams. In the index page of Examinations (app/views/examinations/index.html.erb) I want to add a "Register" button just next to default Show, Edit and Destroy buttons for each exam. When user click to "Register" button I want them to see a page where they can choose their exam language preference and submit their registrations.
Also I want a User can only 1 time register for an exam. I mean they can register for many exams but only 1 time for each.
I read the whole Rails Guide but couldn't find the right answer.
How can I do this kind of application?
Well, you're not going to find the answer for your specific question in rails guides. :)
I'd suggest that you read more on:
Validate uniqueness of an attribute using the scope option
Nested resources

Correct Routing for :has_many :through

I am trying to set up a Many-to-Many association between 2 objects. I have gone through several tutorials and have been able to correctly set up the model. My problem is that I am having trouble setting up the correct routes so I can view the full relationship... something like only displaying the products from a specific category (/categories/1/products/)
This is how I generated the model:
script/generate scaffold category name:string
script/generate scaffold product name:string
script/generate scaffold categorization category_id:integer product_id:integer
Here is the schema:
ActiveRecord::Schema.define(:version => 20100205210519) do
create_table "categories", :force => true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "categorizations", :force => true do |t|
t.integer "category_id"
t.integer "product_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "products", :force => true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
end
Here is the 3 model objects:
class Category < ActiveRecord::Base
has_many :categorizations
has_many :products, :through => :categorizations
end
class Product < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations
end
class Categorization < ActiveRecord::Base
belongs_to :product
belongs_to :category
end
Pretty simple and everything seems to be working fine because I can add a product to a category through the console:
#category.categorizations << Categorization.new(:product_id => 1)
I'm sure I need to update the routes.rb file but I don't really know the correct way to do it. This is what I put in the routes file:
map.resources :categories, :has_many => :products
When I try to view products on a category "/categories/7/products/" it just lists all of the products! Does this mean my route is set up correctly and I just need to write a custom action on the products controller (instead of it going to index)? What am I doing wrong here... am I close or way off?!?
Thanks
You're probably not using the data from the route to filter your list of products.
In your index method of the product_controller, you need to do something like:
Category.find(params[:category_id]).products
What you want to do is use nested resources. The general format looks like this:
map.resources :users do |users|
users.resources :posts
end
Read more about it here.
As proposed in this question, you could try adding the request parameter :category_id to your find query.
Always look at the output of rake routes.

Resources