Rails 5.2 ActiveStorage with UUIDs on Postgresql - ruby-on-rails

We have our app using uuids are primary keys, on a Postgresql Database. (Standard setup described here).
We integrated ActiveStorage following the process described here. A standard setup using rails active_storage:install and migrated using rails db:migrate.
We have a model & corresponding controller as follows:
# Model
class Message < ApplicationRecord
has_one_attached :image
def filename
image&.attachment&.blob&.filename
end
end
# Controller
class MessagesController < ApplicationController
def create
message = Message.create!(message_params)
redirect_to message
end
private
def message_params
params.require(:message).permit(:title, :content, :image)
end
end
We observed that the first few sets of image were correctly associated with the model instances, but then we used to get random images for model instance, or got no image at all. Every time, we restart the server, we get first few images right, but then it was unpredictable.
Unsure, of what's going wrong, we debugged in rails console:
params[:image]
=> #<ActionDispatch::Http::UploadedFile:0x007fcf2fa97b70 #tempfile=#<Tempfile:/var/folders/dt/05ncjr6s52ggc4bk6fs521qw0000gn/T/RackMultipart20180726-8503-vg36kz.pdf>, #original_filename="sample.pdf", #content_type="application/pdf", #headers="Content-Disposition: form-data; name=\"file\"; filename=\"sample.pdf\"\r\nContent-Type: application/pdf\r\n">
On saving the instance and retrieving file name we got a random file, we uploaded previously.
#message = Message.new(message_params)
#message.filename
=> #<ActiveStorage::Filename:0x007fcf32cfd9e8 #filename="sample.pdf">
#message.save
#message.filename
=> #<ActiveStorage::Filename:0x007f82f2ad4ef0 #filename="OtherSamplePdf.pdf">
Looking for an explanation for this weird behaviour, and a possible solution too.

After hours of going line by line in the activestorage source code, and running the same commands
#message = Message.new(message_params)
#message.save
again and again. We got same random results again and again. Then we went through the logs rails printed while attaching the image to message and observed the following:
S3 Storage (363.4ms) Uploaded file to key: KBKeHJARTjnsVjkgSbbii4Bz (checksum: S0GjR1EyvYYbMKh44wqlag==)
ActiveStorage::Blob Create (0.4ms) INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "byte_size", "checksum", "created_at") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" [["key", "KBKeHJARTjnsVjkgSbbii4Bz"], ["filename", "sample.pdf"], ["content_type", "application/pdf"], ["metadata", "{\"identified\":true}"], ["byte_size", 3028], ["checksum", "S0GjR1EyvYYbMKh44wqlag=="], ["created_at", "2018-07-26 04:54:33.029769"]]
ActiveStorage::Attachment Create (2.7ms) INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["name", "file"], ["record_type", "Message"], ["record_id", "534736"], ["blob_id", "0"], ["created_at", "2018-07-26 05:04:35.958831"]]
record_id was being set as 534736, instead of an uuid. Here's where we went wrong.
Active storage was expecting integer foreign key to our Message model, and we wanted it to use uuids instead. So we had to fix our migration, to use uuids instead of integer foreign keys.
Solution:
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
def change
create_table :active_storage_blobs, id: :uuid do |t|
t.string :key, null: false
t.string :filename, null: false
t.string :content_type
t.text :metadata
t.bigint :byte_size, null: false
t.string :checksum, null: false
t.datetime :created_at, null: false
t.index [ :key ], unique: true
end
create_table :active_storage_attachments, id: :uuid do |t|
t.string :name, null: false
t.references :record, null: false, polymorphic: true, index: false, type: :uuid
t.references :blob, null: false, type: :uuid
t.datetime :created_at, null: false
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
end
end
end
Hope this helps, someone facing similar issues. cheers!

I'm late to the party hitting this in 2020 but as anurag mentioned this is due to the active_storage_attachments DB table using a bigint for the record_id. I wasn't able to migrate all the models with ActiveStorage attachments to use UUIDs so I needed a way to support both UUIDs and bigints at the same time.
Warning: If you can avoid this (by migrating everything to UUID most likely) then I'd strongly recommend doing that, and I plan on doing that as soon as we have time.
Warnings aside, migrating the active_storage_attachments table to change the record_id column to be text does work. I had to adjust the few places in our app where we were joining against the active_storage_attachments table using record_id to cast the value to text in the join. For example I used the following code when joining to a model that had a UUID ID.
.joins("
LEFT OUTER JOIN active_storage_attachments
ON active_storage_attachments.record_id = documents.id::text
")
Hopefully this helps someone else who is stuck in the halfway state where not all ActiveStorage using models are using UUIDs or bigint IDs.

I also had this issue. My models all use UUIDs. As I had no records in ActiveStorage needing to be retained, I dropped and recreated the :active_storage_attachments and :active_storage_blobs tables. Here is my migration, in case it's of use to anyone. Using Rails 6.0.4.
def change
reversible do |dir|
dir.up do
drop_table :active_storage_attachments
drop_table :active_storage_blobs
create_table "active_storage_blobs", id: :uuid,
default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string :key, null: false
t.string :filename, null: false
t.string :content_type
t.text :metadata
t.bigint :byte_size, null: false
t.string :checksum, null: false
t.datetime :created_at, null: false
t.index [ :key ], unique: true
end
create_table "active_storage_attachments", id: :uuid,
default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string :name, null: false
t.references :record, null: false, polymorphic: true, index: false, type: :uuid
t.references :blob, null: false, type: :uuid
t.datetime :created_at, null: false
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
dir.down do
drop_table :active_storage_attachments
drop_table :active_storage_blobs
# original tables generated by rails
create_table :active_storage_blobs do |t|
t.string :key, null: false
t.string :filename, null: false
t.string :content_type
t.text :metadata
t.bigint :byte_size, null: false
t.string :checksum, null: false
t.datetime :created_at, null: false
t.index [ :key ], unique: true
end
create_table :active_storage_attachments do |t|
t.string :name, null: false
t.references :record, null: false, polymorphic: true, index: false
t.references :blob, null: false
t.datetime :created_at, null: false
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end
end

Related

Rails Active Storage not working every time. Some time it works, sometime it doesn't

I am using Active Storage to store images for match score. My class looks like this
class TournamentMatch < ApplicationRecords
has_many_attached :score_cards
end
My Score card controller looks like this
class Public::ScoreCardsController < Public::BaseController
def new
session[:tournament_match_id] = params[:tournament_match_id]
end
def create
tournament_match_id = session[:tournament_match_id]
tournament_id = session[:tournament_id]
session[:tournament_match_id] = nil
session[:tournament_id] = nil
tournament_match = TournamentMatch.find_by(id: tournament_match_id)
if tournament_match.score_cards.attach(params['score_cards'])
flash[:notice] = 'Score card uploaded'
else
flash[:error] = 'Score card upload failed'
end
redirect_to public_results_path(tournament_id: "#{tournament_id}/", anchor: 'results')
end
def view
#tournament_match = TournamentMatch.find_by(id: params[:tournament_match_id])
render layout: "application"
end
end
So in my create action,I am finding TournamentMatch by id and then attaching score card to it. I am using same image file each time for uploading. But sometimes image is getting attached and sometime it doesn't. tournament_match.score_cards.attach(params['score_cards'] always returns true in controller. But if i find the same tournament_match and run tournament_match.score_cards.attach(params['score_cards'] in console then it's returning false. I have also noticed that record_id for active_storage_attachments is set to 0 when ever image is not saved. But if image is saved then record_id have value greater than 0.
I have run out of ideas for debugging. Please suggest me how to debug this problem.
Update: below query is from log file, it shows record_id is nil
ActiveStorage::Attachment Exists? (0.6ms) SELECT 1 AS one FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3 LIMIT $4 [["record_id", nil], ["record_type", "TournamentMatch"], ["name", "score_cards"], ["LIMIT", 1]]
My main problem was that I am using :uuid for primary key for all my tables so I has to edit ActiveStorage migration to use :uuid. I had changed mainly these 2 lines t.references :record, null: false, polymorphic: true, index: false, type: :uuid and t.references :blob, null: false, type: :uuid. My final migration looks like this. And after this ActiveStorage is now woking fine.
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
def change
create_table :active_storage_blobs, id: :uuid do |t|
t.string :key, null: false
t.string :filename, null: false
t.string :content_type
t.text :metadata
t.bigint :byte_size, null: false
t.string :checksum, null: false
t.datetime :created_at, null: false
t.index [ :key ], unique: true
end
create_table :active_storage_attachments, id: :uuid do |t|
t.string :name, null: false
t.references :record, null: false, polymorphic: true, index: false, type: :uuid
t.references :blob, null: false, type: :uuid
t.datetime :created_at, null: false
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end
For new rails 7 apps, inside the ActiveStorage migration file, change this
primary_key_type, foreign_key_type = primary_and_foreign_key_types
to this
primary_key_type, foreign_key_type = [:uuid, :uuid]
It will work now.
Notes
I think this means if you want attachments on any model that has uuid as its primary key, then you'll need to make sure all models which you intend to have attachments also use uuid i.e. you can't mix and match (at least, I don't know how).
See here for how to set uuid as the default primary key for all generators across the application.
References
The ActiveStorage docs says:
If you are using UUIDs instead of integers as the primary key on your models you will need to change the column type of active_storage_attachments.record_id and active_storage_variant_records.id in the generated migration accordingly.
More reading here for older rails versions.

ActiveRecord::NotNullViolation in ReviewsController#create

Receiving this error in Rails:
PG::NotNullViolation: ERROR: null value in column "reviewer_id" violates not-null constraint DETAIL: Failing row contains (26, 2222, 2222, 8, null, 2021-01-30 19:26:03.354983, 2021-01-30 19:26:03.354983).
This app is trying to allow users to give other users reviews.
I am new, so the error may be quite obvious, but not to the untrained eye. After hours of research, this was my last resort.
I'd appreciate in help fixing this. God bless.
routes.rb
resources :users do
resources :reviews, only: [ :new, :create ]
end
reviews_controller.rb
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
if #review.save
redirect_to user_path(current_user), notice: 'Review added!'
else
render :new
end
end
private
def review_params
params.require(:review).permit(:rating, :content)
end
schema.rb
create_table "reviews", force: :cascade do |t|
t.text "content"
t.integer "rating"
t.bigint "user_id", null: false
t.bigint "reviewer_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["reviewer_id"], name: "index_reviews_on_reviewer_id"
t.index ["user_id"], name: "index_reviews_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "username"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["username"], name: "index_users_on_username", unique: true
end
add_foreign_key "offers", "games"
add_foreign_key "offers", "users"
add_foreign_key "rentals", "offers"
add_foreign_key "rentals", "users"
add_foreign_key "reviews", "users"
add_foreign_key "reviews", "users", column: "reviewer_id"
user.rb
has_many :reviews, dependent: :destroy
has_many :given_reviews, source: :reviews, foreign_key: :reviewer_id
has_many :received_reviews, source: :reviews, foreign_key: :user_id
review.rb
belongs_to :user
validates :content, presence: true
validates :rating, presence: true
You have a database constraint on Review, for the reviewer_id field, defined in your schema here:
t.bigint "reviewer_id", null: false
In your controller#create action you are defining a user_id, but I don't see where you are passing in the reviewer_id or setting it. If you have a field in the form, you'll need to add that param to the review_params so it can be passed from the form to the controller action. Or you'll need to define it in the create action similar to how you've defined the user_id.
Since your reviewer is the person leaving the review, and the User is being reviewed, I would do this:
In your view where you are leaving the review, (I assume this would be User#show) you are viewing a specific user. You know which user this is and most likely in that view it is defined as #user, so you can most likely use <%= hidden_field_tag :user_id, #user.id %> in the form for the review. This will pass user_id to the Reviews#create action in the params. You will also need to add user_id to review_params so it can be accessed in the #create action.
Then in your Reviews#create action, assign #review.reviewer_id = current_user.id. #review.user_id will be assigned when you call Review.create(review_params) assuming you have added it the the form and the review_params as suggested above.
Try adding a breakpoint in the create action and look at the params object getting sent from the review form, this may be helpful for you to understand what's happening.
Typical Rails practice is to use a model validation as well. This way you'd get an app error before the request ever hits the database.

How do I fix an error SQLite3 :: ConstraintException: NOT NULL with devise in rails?

I am implementing an authentication system with devise in rails, but when registering the user, in this case a student, the following error is skipped:
ActiveRecord::NotNullViolation in Devise::RegistrationsController#create
SQLite3::ConstraintException: NOT NULL constraint failed: students.codigo: INSERT INTO "students" ("email", "encrypted_password", "created_at", "updated_at") VALUES (?, ?, ?, ?)
I have been solving this error all day, but nothing that I can solve. I have implemented a before_action in the students_controller controller:
class StudentsController < ApplicationController
before_action :configure_devise_params, if: :devise_controller?
def configure_devise_params
devise_parameter_sanitizer.permit(:sign_up) do |user|
user.permit(:codigo, :documento, :nombres, :apellidos, :es_egresado, :email, :password, :password_confirmation)
end
end
...
end
but still the problem is not solved.
The same error happens when I uninstall devise, remove all reference from it, and also, implement strong params in the controller, without this the registry works satisfactorily. But with strong params and devise does not work, it passes data to null in the SQL command.
The student database is the following:
create_table "students", id: false, force: :cascade do |t|
t.integer "codigo", null: false
t.integer "documento", null: false
t.string "nombres", null: false
t.string "apellidos", null: false
t.integer "es_egresado", null: false
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.decimal "promedio_carrera"
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["documento"], name: "index_students_on_documento", unique: true
t.index ["email"], name: "index_students_on_email", unique: true
t.index ["reset_password_token"], name: "index_students_on_reset_password_token", unique: true
end
And its implementation migrate is the following:
class DeviseCreateStudents < ActiveRecord::Migration[5.1]
def change
create_table :students, {
:id => false,
:primary_key => :codigo
} do |t|
## Database authenticatable
t.integer :codigo, null: false
t.integer :documento, null: false
t.string :nombres, null: false
t.string :apellidos, null: false
t.integer :es_egresado, null: false
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
t.decimal :promedio_carrera, null: true
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
# t.integer :sign_in_count, default: 0, null: false
# t.datetime :current_sign_in_at
# t.datetime :last_sign_in_at
# t.string :current_sign_in_ip
# t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps null: false
end
add_index :students, :email, unique: true
add_index :students, :reset_password_token, unique: true
add_index :students, :documento, unique: true
# add_index :students, :confirmation_token, unique: true
# add_index :students, :unlock_token, unique: true
end
end
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"6zdENP1iREYYM+qpqtskqRJ+aB38NIX/nG9jFsoGlImqUVyS9bsmBF6Uc7xvDD/J50/zamlZbbm2rwAaCgOmuw==",
"student"=>
{"codigo"=>"625762",
"documento"=>"107526792",
"nombres"=>"Carlos",
"apellidos"=>"Garnica",
"es_egresado"=>"0",
"email"=>"wcarlosfg.1234567890#hotmail.com",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"},
"commit"=>"Registrar"}
I also clarify that when I create another Student table, removed the previous one, and I remove the null option to the fields, the error does not appear, but the fields that supposedly have been saved remain in NULL in the database
UPDATE: Modify the database, making all fields null, and the form sends null fields whether they have data in the form or not, it is very rare. When doing inspect on the object, it shows the following:
irb(main):002:0> Student.last
Student Load (0.0ms) SELECT "students".* FROM "students" ORDER BY "students"."id" DESC LIMIT ? [["LIMIT", 1]]
=> #<Student id: 2, codigo: nil, documento: nil, nombres: nil, apellidos: nil, es_egresado: nil, promedio_carrera: nil, email: "wcarlosfg.1234567890#hotmail.com", created_at: "2018-09-30 07:46:39", updated_at: "2018-09-30 07:46:39">
SOLUTION: After a day looking at the error, the only solution was to overwrite the devise driver for records, with this, I was able to add the logic and implement strong params to make the registry work correctly.

ActiveModel::MissingAttributeError (can't write unknown attribute `user_id`)

I have problems with my comments when my app is deployed. Locally everything is working. The logs from heroku says:
ActiveModel::MissingAttributeError (can't write unknown attribute `user_id`):
2018-01-02T15:52:43.461794+00:00 app[web.1]: [79cd7190-e2d9-4dd0-bf71-
709552e5c6e5] app/controllers/comments_controller.rb:15:in `create'
I have no ideas what is occuring the error. Maybe some database thing?
My CommentsController
class CommentsController < ApplicationController
def create
#post =Post.find(params[:post_id])
#comment =#post.comments.create(params[:comment].permit(:name, :body).merge(user: current_user))
redirect_to post_path(#post)
end
def destroy
#post = Post.find(params[:post_id])
#comment= #post.comments.find(params[:id])
if current_user.id == #comment.user_id
#comment.destroy
end
redirect_to post_path(#post)
end
end
My Models
class User < ApplicationRecord
has_many :posts
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
class Post < ApplicationRecord
belongs_to :user, optional: true
has_many :comments, dependent: :destroy
validates :title, presence: true, length: {minimum: 5}
validates :body, presence: true
end
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
end
My migration-file
class CreateComments < ActiveRecord::Migration[5.1]
def change
create_table :comments do |t|
t.string :name
t.text :body
t.references :post, index: true
t.timestamps
end
end
end
if you need more code or have any ideas please let me know
EDIT: if i add a user_id column i get a SQLite3::SQLException: duplicate column name: user_id: ALTER TABLE "comments" ADD "user_id" integer error
My schema.rb
`create_table "comments", force: :cascade do |t|
t.string "name"
t.text "body"
t.integer "post_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.index ["post_id"], name: "index_comments_on_post_id"
end
create_table "posts", force: :cascade do |t|
t.string "title"
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
t.string "theme"
t.integer "user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
You'll need to add a user_id column to your comments table. The belongs_to requires this. You're also going to need a post_id column, and user_id for your posts table to.
You can customise the column name, but the convention is to use the format parent_table_id.
Here's the key quote, from the docs:
Associations are implemented using macro-style calls, so that you can
declaratively add features to your models. For example, by declaring
that one model belongs_to another, you instruct Rails to maintain
Primary Key-Foreign Key information between instances of the two
models, and you also get a number of utility methods added to your
model.
This means, for example, if your first user has an id of 1, all of their comments and posts will have a user_id value of 1, which does the actual tying together of the records.
Here's an example migration with the relevant line included:
def change
create_table :comments do |t|
...
t.belongs_to :user, index: true
...
end
end
Does that make sense? Let me know if you've any questions and I can update as needed :)
You need to add user_id
Create the migration with
rails g migration AddUserIdToComment user:references
Then do
rake db:migrate
And you should be fine.
Your migration have missing
t.references :user, index: true
So you need to add user_id column within comments table
Update : It seems like you have some migration problem. I suggest you to check for rake db:migrate:status comment and look for any down migration. Once all are up then just run rake db:migrate:down VERSION='VERSION_NUMBER_HERE' and add your user t.references :user, index: true to the same migration and migrate.
PS: Change existing migration if and only if you have not pushed it.

Ruby on Rails SQLite3::ConstraintException: NOT NULL constraint failed:

I am developing a simple app where a user can add a subject to a cart. Before I add the authentication I was able to add a subject to the cart but as I want a user to be able to has access to just his/her cart I used Devise to create User with authentication. Now, when I click on the button to add a subject to the cart I have the following error:
This is a snapshot of the error I get: 1
SQLite3::ConstraintException: NOT NULL constraint failed: carts.user_id: INSERT INTO "carts" ("created_at", "updated_at") VALUES (?, ?)
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
private
def current_cart
Cart.find(params[:user_id])
rescue ActiveRecord::RecordNotFound
cart = Cart.create
params[:user_id] = cart.id
cart
end
end
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :cart
end
class Cart < ActiveRecord::Base
belongs_to :user
has_many :line_items, dependent: :destroy
scope :user_carts, ->(user) { where(['user_id= ?', user.id]) }
end
class AddUsersToCarts < ActiveRecord::Migration
def up
add_reference :carts, :user, index: true
Cart.reset_column_information
user = User.first
Cart.all.each do |cart|
cart.user_id = user.id
cart.save!
end
change_column_null :carts, :user_id, false
add_foreign_key :carts, :users
end
def down
remove_foreign_key :carts, :users
remove_reference :carts, :user, index: true
end
end
Edit: I added the schema.rb below:
ActiveRecord::Schema.define(version: 20151210213408) do
create_table "carts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id", null: false
end
add_index "carts", ["user_id"], name: "index_carts_on_user_id"
create_table "line_items", force: :cascade do |t|
t.integer "subject_id"
t.integer "cart_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "subjects", force: :cascade do |t|
t.string "title", null: false
t.string "code", null: false
t.text "description", null: false
t.integer "credits", null: false
t.string "lecturer", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "subjects", ["title"], name: "index_subjects_on_title", unique: true
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
You current_cart method does not make much sense.
You cannot find the user's cart by calling Cart.find(params[:user_id]), because that looks for a cart by an id (not by an user_id).
Cart.create fails, because you do not provide an user_id that is required (your database migrations says that the filed cannot be null).
Furthermore, params[:user_id] = cart.id changes the params hash, but not the new cart.
Change that method to something like this (using find_or_create_by) and use the current_user.id instead of params[:user_id]:
def current_cart
Cart.find_or_create_by(user_id: current_user.id)
end

Resources