I'm creating an expense tracker application using Ruby on Rails 7.0. I have 3 models Account, Category, and Transaction.
This is how transactions migration looks:
class CreateTransactions < ActiveRecord::Migration[7.0]
def change
create_enum :transaction_type, %w[debit credit]
create_table :transactions do |t|
t.datetime :date
t.string :description
t.enum(:transaction_type, enum_type: 'transaction_type', null: false)
t.decimal :amount
t.string :notes
t.references :account, null: false, foreign_key: true
t.references :category, null: false, foreign_key: true
t.timestamps
end
end
end
Below is the Transaction model.
class Transaction < ApplicationRecord
belongs_to :account
belongs_to :category
enum :transaction_type, { debit: 'DEBIT', credit: 'CREDIT' }, prefix: true
end
I followed this video tutorial and defined enums that way. But when I hang around with the Transaction model on the rails console, it gives me this error Object doesn't support #inspect error
For example, Transaction.transaction_type_debit should return all the transactions with transaction_type of debit since prefix: true enables those methods. But it gives me the above error instead.
I suspect there's something with the associations with the other two models but still not sure exactly why and how to fix it.
Finally, I figured out that the issue is with irb. What I did was just added gem 'pry-rails' and it replaced irb with pry. So, I'm not getting the error anymore with pry.
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 have a model Member that has a new creation form. The form allows users to assign a department and position for each member to be a part of. The positions are relative to the department. In a separate part of the app, users can create departments and create/assign positions. I am trying to create a grouped_collection_select for the new members page, however my positions are not showing up, just departments as categories. I believe this is an association issue, where the positions are not being associated with their respective dept. I have the string department_id in my positions model, however I don't think the selection is able to read that as the parent. If anyone can point me in the correct direction that'd be awesome.
The line giving an error: (from the new members form_for)
<%= f.grouped_collection_select :title, Department.where(production_id: current_user.default_working_production_id).order(:department), :positions, :department, :id, :position, include_blank: true %>
My schema looks like:
create_table "departments", force: true do |t|
t.string "department"
t.datetime "created_at"
t.datetime "updated_at"
t.string "production_id"
end
create_table "positions", force: true do |t|
t.string "position"
t.string "department_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "production_id"
end
My model associations:
class Member < ActiveRecord::Base
belongs_to :company
validates :firstname, presence: true
validates :email, presence: true
def name
"#{firstname} #{lastname}"
end
end
class Department < ActiveRecord::Base
has_many :positions
attr_accessible :department
validates :department, presence: true
end
class Position < ActiveRecord::Base
belongs_to :department
attr_accessible :department_id, :position, :department
end
The primary keys (id) of each Model are integers, but the foreign keys for the associations are defined in your migrations as strings. (department_id, production_id). This may be causing problems when Rails tries to make the associations because for example, "1" == 1 evaluates to false. Change the foreign keys to integers via a new migration or by editing the existing migration and resetting the db.
You can confirm if you've fixed the association with the rails console:
$> rails console
Running via Spring preloader in process 20933
Loading development environment (Rails 4.2.5)
irb(main):001:0> Department.first.positions
This will query the First department in the db for all of its positions. If it throws an error then the association is not setup properly. If it returns a collection of positions, the association works.
I'm building a rails website, which involves a directed friendship relation. I know in model level, it is a self referential association. And there are methods like has_and_belongs_to for that association.
My question is: how can I set up the database level constraints for this relation. I guess the migration would be something like this, which uses foreign keys to guarantee the referential integrity:
class CreateFriendships < ActiveRecord::Migration
def change
create_table :friendships do |t|
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :user, null: false, foreign_key: true
t.integer :accepted, null: false, default: 0
end
end
But when I run rake db:migrate, it has error:
PG::DuplicateObject: ERROR: constraint "fk_friendships_user_id" for relation "friendships" already exists
As a matter of fact, I'm not even sure whether it is necessary for me to set up the database constraint in this case, since I've seen some people's implementation of friendship relation has no database constraint like this:
create_table :friendships do |t|
t.integer :user_id
t.integer :friend_id
t.timestamps
end
According to Rails Guide
The Active Record way claims that intelligence belongs in your models, not in the database. As such, features such as triggers or constraints, which push some of that intelligence back into the database, are not heavily used.
I'm not sure whether in this case, the database constraints are heavily used.
So is it really necessary for me to set up database level constraints (using foreign keys) in this case? Or I just need to realize the constraints in model level? Thanks!!
You have declared user relation twice:
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :user, null: false, foreign_key: true
Seems that it should be like this:
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :friend, null: false, foreign_key: true
To answer your question: how can I set up the database level constraints for this relation?
Answer: Just like you already have.
Often developers go the rails way and set these constraints in model, but it's perfectly reasonable to set them up in database.
EDIT:
This will let you create a table with friend_id
class CreateFriendships < ActiveRecord::Migration
def change
create_table :friendships do |t|
t.belongs_to :user, null: false, foreign_key: true
t.integer :friend_id, null: false
t.integer :accepted, null: false, default: 0
end
add_foreign_key :friendships, :users, column: :friend_id
end
end
I think you're getting confused about the role of foreign_keys in your database architecture.
ActiveRecord is just a "coating" for SQL.
It's able to form queries etc which allow you to build associated objects, thus the most important thing you can do is associate those objects properly.
The way to do this - in SQL - is to use a foreign_key, which essentially shows the likes of ActiveRecord (and SQL if you use a join query) which data is associated:
Foreign keys are a standard element of relational database structures, which you probably know.
The reason why your data structure is failing is due to the fact you've replicated the user_id foreign key in your friendships table.
You'll want to refer to the following:
Rails: self join scheme with has_and_belongs_to_many?
This shows you that if you want to create a self referential join table (such as you're doing), you need to use the following:
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :friends,
class_name: "User",
join_table: :friendships,
foreign_key: :user_id,
association_foreign_key: :friend_user_id
end
#db/migrate/______.rb
class CreateFriendships < ActiveRecord::Migration
def self.up
create_table :friendships, id: false do |t|
t.integer :user_id
t.integer :friend_user_id
end
add_index(:friendships, [:user_id, :friend_user_id], :unique => true)
add_index(:friendships, [:friend_user_id, :user_id], :unique => true)
end
def self.down
remove_index(:friendships, [:friend_user_id, :user_id])
remove_index(:friendships, [:user_id, :friend_user_id])
drop_table :friendships
end
end
Notice how the references are for user_id and friend_user_id?
These are the two foreign keys you need to make sure your has_and_belongs_to_many is able to associate two objects of the same model.
I am attempting to create a Collaboration table in my Rails 4 project, but I've run into an issue. I wish it to belong_to a single user, the collaborator.
I ran the following command to generate the model and the migration, which I've also copied below.
rails generate model Collaboration project:references collaborator:references accepted:boolean
Migration:
class CreateCollaborations < ActiveRecord::Migration
def change
create_table :collaborations do |t|
t.references :project, index: true, foreign_key: true
t.references :collaborator, index: true, foreign_key: true
t.boolean :accepted
t.timestamps null: false
end
end
end
Model:
class Collaboration < ActiveRecord::Base
belongs_to :project
belongs_to :collaborator, class_name: 'User'
end
I updated the Collaboration model to include , class_name: 'User' as shown above. Similarly, I updated the existing Strategy model to include a has_many :collaborations
class Project < ActiveRecord::Base
has_many :collaborations
end
When I run rake db:migrate, I get the following error reported.
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
PG::UndefinedTable: ERROR: relation "collaborators" does not exist
I'm a bit puzzled as to wy this is happening. Any assistance would be greatly appreciated! Thank you. :)
EDIT:
Adding code for my User model as well.
class User < ActiveRecord::Base
authenticates_with_sorcery!
has_many :projects
has_many :collaborations
end
I edited out validations for fields such as password, email, etc to try to remove clutter.
This part of your migration:
t.references :collaborator, index: true, foreign_key: true
will try to create a foreign key inside the database so that the collaborator_id column of the collaborations table will be guaranteed to be NULL or contain the id of a column in the collaborators table. You can't create that FK until the collaborators table exists.
The error you're getting is:
relation "collaborators" does not exist
and that's just telling you that you don't have a collaborators table but you're trying to reference it.
You need a migration to create the collaborators table before you create your collaborations table.
In Rails 5, at least, you can use foreign_key: {to_table: ... }} as follows.
create_table :messages, id: :uuid do |t|
t.references :from_user, type: :uuid, index: true, null: false, foreign_key: {to_table: :users, on_delete: :cascade}
t.references :to_user, type: :uuid, references: :user, index: true, null: false, foreign_key: {to_table: :users, on_delete: :cascade}
t.text :body, null: false
t.timestamps
end
sorry for being late, but essentially it's all about convenience, remember that's the essence of rails. so; every reference should be targeting the table that should be in the plural (since a table holds many "objects") therefore, you must make the reference to plural so rails will generate a reference to a singular object. button line, your migration should look more like;
class CreateCollaborations < ActiveRecord::Migration
def change
create_table :collaborations do |t|
t.references :projects, index: true, foreign_key: true
t.references :collaborators, index: true, foreign_key: true
t.boolean :accepted
t.timestamps null: false
end
end
end
Now, if you follow the conventions, then you should have no problem with the rest, just keep in mind that belong_to is to a singular object and has_many is to a plural object.
PS: I would not use past reference for the column, like accepted
Happy Coding
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?