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).
Related
I have a folder named admin that has a generated scaffolding in it named products that also has the primary_key, id, changed to ect. I then created a model called cart_products that has a belongs_to :product. When I try to use it like:
#cart.cart_products.create(product: #product, quantity:), it throws a name error, saying
Rails couldn't find a valid model for Product association. Please provide the :class_name option on the association declaration. If :class_name is already provided, make sure it's an ActiveRecord::Base subclass.
So I then changed the belongs_to to belongs_to :product, :class_name => "Admin::Product" which is the name of the product model. Now I am getting an
ActiveRecord::StatementInvalid - SQLite3::SQLException: no such table: main.products
Where did main.products come from when in my database it is saved as create_table "admin_products", primary_key: "ect", force: :cascade do |t|?
This is what My code looks like:
# controllers/home/cart_controller.rb
class Home::CartController < HomeController
def add
#product = Admin::Product.find_by(ect: params[:ect])
# Code breaks on next line
#cart.cart_products.create(product: #product, quantity:)
end
end
# models/cart_product.rb
class CartProduct < ApplicationRecord
belongs_to :product, class_name: "Admin::Product"
belongs_to :cart
end
# models/admin/product.rb
class Admin::Product < ApplicationRecord
has_many :cart_products
has_many :carts, through: :cart_products
end
end
# models/admin.rb
module Admin
def self.table_name_prefix
"admin_"
end
end
The Database that I am trying to access is:
# associated with models/admin/product.rb
create_table "admin_products", primary_key: "ect", force: :cascade do |t|
t.string "title"
t.decimal "price"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
# associated with models/cart_product.rb
class CreateCartProducts < ActiveRecord::Migration[7.0]
def change
create_table :cart_products do |t|
t.belongs_to :product, null: false, foreign_key: true
t.belongs_to :cart, null: false, foreign_key: true
t.integer :quantity
t.timestamps
end
end
end
You have to tell rails the table name:
# app/models/admin/product.rb
module Admin
class Product < ApplicationRecord
self.table_name = "admin_products"
end
end
Or add prefix to every table for models in Admin module.
# app/models/admin.rb
module Admin
def self.table_name_prefix
"admin_"
end
end
Update
Rollback your CreateCartProducts migrations and update it to fix foreign key constraint:
# NOTE: by default when foreign key constraint is created
# the name of the foreign table is inferred from
# the argument `:product`. There is no `products` table,
# which is why SQLite complains about it in the error;
# custom primary key has to be also specified.
# t.belongs_to :product, null: false, foreign_key: true
t.belongs_to :product, null: false,
foreign_key: { to_table: :admin_products, primary_key: "ect" }
Run migrations again. This should fix it.
Also, it's probably best to set up PostgreSQL for development. It would have raised an error when trying to run that migration, but SQLite seems to be ok with migration but complains later.
https://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html#method-i-table_name
https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference
Always run after scaffolding with any model
rails db:migrate
Is your model file admin/product.rb
class Admin::Product < ApplicationRecord
If yes, then you need to have class_name with associations as below
belongs_to :product, class_name: "Admin::Product"
I am having trouble deciding which type of association fits best for my application. I have the following models: Firm, Client, and Case.
A Firm handles many Cases. And each Case can have one or more Clients assigned to it. Therefore my initial approach was:
class Firm < ActiveRecord::Base
has_many :cases
has_many :clients
end
class Case < ActiveRecord::Base
belongs_to :firm
has_many :clients
end
class Client < ActiveRecord::Base
belongs_to :firm
has_many :cases
end
But I think something is not right with this. I was thinking a has_many :through Association would be better but I am not sure.
Rails Guides says that “You should use has_many :through if you need validations, callbacks, or extra attributes on the join model”. As the great coding philosopher Avi Flombaum once eluded, how can you possibly know that your join model will not serve an additional purpose this early in the application process. No matter where you are in the development stage, you can never see so far in the future to know you will not need to extend the join table.
Why You Don’t Need Has_and_belongs_to_many Relationships
With that out of the way:
# models/firm.rb
class Firm < ActiveRecord::Base
has_many :clients
has_many :cases, through: :clients
end
# models/client.rb
class Client < ActiveRecord::Base
belongs_to :firm
has_many :client_cases
has_many :cases, through: :client_cases
end
# models/case.rb
class Case < ActiveRecord::Base
has_many :client_cases
has_many :clients, through: :client_cases
end
# models/client_case.rb
class ClientCase < ActiveRecord::Base
belongs_to :client
belongs_to :case
end
UPDATE
Your issue must lie somewhere else; I was able to create the following in a terminal without any errors.
f = Firm.create name: 'Magical Group' # => #<Firm id: 1...
b = f.clients.create name: 'Billy' # => #<Client id: 1...
m = f.clients.create name: 'Megan' # => #<Client id: 2...
c = Case.create name: 'Billy & Megan are getting married!' # => #<Case id: 1...
b.cases << c # => #<ActiveRecord::Associations::CollectionProxy
m.cases << c # => #<ActiveRecord::Associations::CollectionProxy
f.cases.count # => 2
f.cases.uniq.count # => 1
I've provide the database schema, make sure yours resembles:
ActiveRecord::Schema.define(version: 20150813173900) do
create_table "cases", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "client_cases", force: :cascade do |t|
t.integer "client_id"
t.integer "case_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "client_cases", ["case_id"], name: "index_client_cases_on_case_id"
add_index "client_cases", ["client_id"], name: "index_client_cases_on_client_id"
create_table "clients", force: :cascade do |t|
t.string "name"
t.integer "firm_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "clients", ["firm_id"], name: "index_clients_on_firm_id"
create_table "firms", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Assuming that a case will never belong to a different firm:
class Firm < ActiveRecord::Base
has_many :clients
has_many :cases, through: :clients
end
class Client < ActiveRecord::Base
belongs_to :firm
has_and_belongs_to_many :cases
end
class Case < ActiveRecord::Base
has_and_belongs_to_many :clients
end
Migrate join table
rails g migration CreateJoinTableCaseClient case client
I have quite the Problem with my has_many :through association.
The models look like this:
class User < ActiveRecord::Base
has_many :roles
has_many :datasets, through: :roles
has_secure_password
end
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :dataset
end
class Dataset < ActiveRecord::Base
has_many :roles
has_many :users, through: :roles
end
I want to add a record to roles every time a new Dataset is created. It should
be created with the new Dataset, an existing User 'Admin' and the column name of Role should be set to 'Admin'.
I tried everything I found on Stackoverflow but nothing works for me.
My create method in the DatasetController looks this:
def create
#dataset = Dataset.new(dataset_params)
#dataset.save
#user = User.find_by(name: 'Admin')
#dataset.users << #user
##dataset.roles.create(user: #user, dataset: #dataset, name: 'Admin')
respond_with(#dataset)
end
I tried both the << operator and the create method.
the first results in:
ActiveRecord::UnknownAttributeError in DatasetsController#create
unknown attribute: dataset_id
the second in:
ActiveModel::MissingAttributeError in DatasetsController#create
can't write unknown attribute `user_id
Does anyone know why I get these errors?
my schema.rb:
create_table "datasets", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "roles", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", force: true do |t|
t.string "name"
t.string "mail"
t.string "password_digest"
t.datetime "created_at"
t.datetime "updated_at"
end
Your Role model needs columns to refer to which User and Dataset it belongs. Without these it has no idea who belongs to who.
So you simply need to create a migration to add these columns:
class AddRefererColumnsToRole < ActiveRecord::Migration
def change
add_column :roles, :user_id, :integer
add_column :roles, :dataset_id, :integer
end
end
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 have created a blank rails app (rails new cheese_shop), with two models and a join table. I am trying to create a cheese shop and specifying which cheeses it contains, at creation time, like this:
cheeses = [
Cheese.create!(name: "Bree"),
Cheese.create!(name: "Kačkavalj"),
]
Shop.create! name: "The Whistling Cheese", cheeses: cheeses
However, I'm getting this error:
SQLite3::ConstraintException: NOT NULL constraint failed: stocks.shop_id: INSERT INTO "stocks" ("cheese_id", "created_at", "updated_at") VALUES (?, ?, ?)
Apparently, the shop ID is not inserted to the stocks table when I create the shop. Is it possible to fix this, without having to do it in two steps (i.e. without first creating the Shop, and then adding the cheeses?)
Here are my models:
class Cheese < ActiveRecord::Base
has_many :shops, through: :stocks
has_many :stocks
end
class Shop < ActiveRecord::Base
has_many :cheeses, through: :stocks
has_many :stocks
end
class Stock < ActiveRecord::Base
belongs_to :shop
belongs_to :cheese
end
My migrations look like this:
class CreateTables < ActiveRecord::Migration
def change
create_table :cheeses do |t|
t.string :name, null: false
t.timestamps null: false
end
create_table :shops do |t|
t.string :name, null: false
t.timestamps null: false
end
create_table :stocks do |t|
t.integer :shop_id, null: false
t.integer :cheese_id, null: false
t.integer :amount
t.float :price
end
end
end
maybe you should try to use nested attributes:
class Shop < ActiveRecord::Base
has_many :cheeses, through: :stocks
has_many :stocks
accepts_nested_attributes_for :stocks
end
and then you will be able to do something like:
cheese = Cheese.create!(name: "Bree")
params = { attrib: { name: "The Whistling Cheese", stocks_attributes: { cheese_id: cheese.id} } }
Shop.create params[:attrib]
here is doc: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
It turns out Rails creates the associations in two steps, first leaving out the Shop ID, then setting the Shop IDs with an UPDATE, all in one transaction. So The NOT NULL constraints are causing the problem.
Changing this:
t.integer :shop_id, null: false
t.integer :cheese_id, null: false
…to this:
t.integer :shop_id
t.integer :cheese_id, null: false
…solves the problem, although I'm unhappy with this since now I cannot rely on the database to ensure the integrity of my data.