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.
Related
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).
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?
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
I have got the following models,
class City < ActiveRecord::Base
belongs_to :state
belongs_to :country
end
class State < ActiveRecord::Base
belongs_to :country
has_many :cities
end
class Country < ActiveRecord::Base
has_many :states
has_many :cities, :through => :state
end
This is my schema.rb,
create_table "cities", :force => true do |t|
t.string "name"
t.string "state_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "countries", :force => true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "states", :force => true do |t|
t.string "name"
t.string "country_id"
t.datetime "created_at"
t.datetime "updated_at"
end
This is my seed data,
country_in = Country.create(name: 'India')
state_ap = country_in.states.create(name: 'Andhra Pradesh')
state_mh = country_in.states.create(name: 'Maharashtra')
city_hyd = state_ap.cities.create(name: 'Hyderabad')
state_ap.cities.create([{name: 'Tirupathi'}, {name: 'Visakhapatnam'}])
state_mh.cities.create([{name: 'Mumbai'}, {name: 'Pune'}, {name: 'Thane'}])
Problem
When I try to find all cities under "India" using
country_in.cities
I get this error : ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :state in model Country
When I try to find which country the city "Hyderabad" is, using
city_hyd.country , I get nil
Why are the links between cities and countries not present?
Are my associations wrong is there something else I missed out on?
The missing link here is the following (see the "Rails Guides for Associations"):
class Country < ActiveRecord::Base
has_many :states
has_many :cities, :through => :states
end
class City < ActiveRecord::Base
belongs_to :state
def country
state ? state.country : nil
end
end
My changes were the following:
has_many :through needs the plural form, not the singular.
If you use belongs_to in City, there needs to be a state_id defined in the table (which would be redundant). So I have replaced it through a getter method. A corresponding setter method is not possible.
I hope that now works for you.
I'm a bit new to rails so if this is very basic please bear with me.
I am creating something like a chat application. My model looks like this (from schema.rb)
create_table "people", :force => true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "session_id"
end
create_table "sessions", :force => true do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.integer "leader_id"
end
I've set up the basic relationship I want -- which is session_id is a foreign key in the people table as such:
class Person < ActiveRecord::Base
belongs_to :session
end
class Session < ActiveRecord::Base
has_many :people
end
I want the leader_id to now be the person who is the "host" of the session (therefore its a foreign key to exactly one person). How do I setup an additional association so I can do:
session = Session.find_by_id(1)
host = Person.new(:name => 'joebob')
session.leader = host
You can tell Rails that the leader_id is really a Person id by specifying the class name in the belongs_to association:
class Session < ActiveRecord::Base
belongs_to :leader, :class_name => "Person"
end