I am finding an unexpected attribute in an ActiveRecord object and I can't figure out where its coming from. Note the final attribute nil => nil.
irb(main):001:0> p = ProgExit.new
=> #<ProgExit id: nil, Student_Bnum: nil, Program_ProgCode: nil, ExitCode_ExitCode: nil, ExitTerm: nil, ExitDate: nil, GPA: nil, GPA_last60: nil, RecommendDate: nil, Details: nil>
irb(main):002:0> p.attributes
=> {"id"=>nil, "Student_Bnum"=>nil, "Program_ProgCode"=>nil, "ExitCode_ExitCode"=>nil, "ExitTerm"=>nil, "ExitDate"=>nil, "GPA"=>nil, "GPA_last60"=>nil, "RecommendDate"=>nil, "Details"=>nil, nil=>nil}
Two other items of note: 1) this problem doesn't present for any other models. 2) this problem is the same for this model across development, test and production. 3) The model does not use attr_accessor anywhere.
Any idea where this comes from? I am including the related model though it is thus far tiny (still working on it).
class ProgExit < ActiveRecord::Base
belongs_to :student, foreign_key: "Student_Bnum"
belongs_to :program, foreign_key: "Program_ProgCode"
belongs_to :exit_code, foreign_key: "ExitCode_ExitCode"
end
And here is the relevant code from my schema.rb
create_table "prog_exits", id: false, force: true do |t|
t.integer "id", null: false
t.string "Student_Bnum", limit: 9, null: false
t.string "Program_ProgCode", limit: 45, null: false
t.string "ExitCode_ExitCode", limit: 45, null: false
t.integer "ExitTerm", null: false
t.datetime "ExitDate"
t.float "GPA", limit: 24
t.float "GPA_last60", limit: 24
t.datetime "RecommendDate"
t.text "Details"
end
add_index "prog_exits", ["ExitCode_ExitCode"], name: "fk_Exit_ExitCode1_idx", using: :btree
add_index "prog_exits", ["Program_ProgCode"], name: "fk_Exit__Program_idx", using: :btree
add_index "prog_exits", ["Student_Bnum"], name: "fk_Exit_Student1_idx", using: :btree
add_index "prog_exits", ["id"], name: "id", using: :btree
I'm not sure what other code to include here but if there is something else that might be the culprit I can share that to.
since you've overriden the default primary key for this table (by including the id: false option in the create_table statement), you need to explicitly define a primary key for this model.
Per Rails docs, you can use self.primary_key= on the model to define the key explicitly.
see related issue: ActiveRecord model without primary key
Related
I am creating the following tables in Rails (with Postgresql):
User: a user can be in one or more groups and can be the owner of one or more groups
Group: a group has an admin (which is one of the users) and one or more users
Participant: a participant the table in between the other two to connect them
The models are like this:
#User.rb
class User < ApplicationRecord
has_many :groups
has_many :participants
end
#Group.rb
class Group < ApplicationRecord
belongs_to :user
has_many :participants
end
#Participant.rb
class Participant < ApplicationRecord
belongs_to :group
belongs_to :user
end
After this, I did some migration to change the name of the user_id that the Group would have, into admin_id:
class ChangeForeignKeyForGroups < ActiveRecord::Migration[6.1]
def change
rename_column :groups, :user_id, :admin_id
end
end
So now, my schema looks like this:
create_table "groups", force: :cascade do |t|
t.string "name"
t.text "description"
t.integer "participants"
t.bigint "admin_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["admin_id"], name: "index_groups_on_admin_id"
end
create_table "participants", force: :cascade do |t|
t.bigint "group_id", null: false
t.bigint "user_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["group_id"], name: "index_participants_on_group_id"
t.index ["user_id"], name: "index_participants_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.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
The problem is that once I try to create a Group in seeds, I keep having this error:
pry(main)> group = Group.new(name: "Test Group", description: "blablabal")
=> #<Group:0x00007fdbcdd03f80 id: nil, name: "Test Group", description: "blablabal", participants: nil, admin_id: nil, created_at: nil, updated_at: nil>
[5] pry(main)> group.valid?
=> false
[6] pry(main)> group.errors
=> #<ActiveModel::Errors:0x00007fdbcdc7ad98
#base=#<Group:0x00007fdbcdd03f80 id: nil, name: "Test Group", description: "blablabal", participants: nil, admin_id: nil, created_at: nil, updated_at: nil>,
#errors=[#<ActiveModel::Error attribute=user, type=blank, options={:message=>:required}>]>
It seems I am missing the user, so I try adding it but it throws the same error:
user = User.first
User Load (1.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 7, email: "jose#test.io", created_at: "2022-05-31 10:29:06.208673000 +0000", updated_at: "2022-05-31 10:29:06.208673000 +0000", username: "Jose">
pry(main)> group = Group.new(name: "Test Group", description: "blablabal", admin_id: user.id)
=> #<Group:0x00007fdbdd205ad8 id: nil, name: "Test Group", description: "blablabal", participants: nil, admin_id: 7, created_at: nil, updated_at: nil>
[13] pry(main)> group.valid?
=> false
[14] pry(main)> group.errors
=> #<ActiveModel::Errors:0x00007fdbdd1d4578
#base=#<Group:0x00007fdbdd205ad8 id: nil, name: "Test Group", description: "blablabal", participants: nil, admin_id: 7, created_at: nil, updated_at: nil>,
#errors=[#<ActiveModel::Error attribute=user, type=blank, options={:message=>:required}>]>
Am I doing something wrong? Have I structure it wrongly? Thanks!
You simply renamed your foreign key but there's more to it that that. In my migration, I would have done something like this:
class ChangeForeignKeyForGroups < ActiveRecord::Migration[6.1]
def change
remove_reference :groups, :user, foreign_key: true
add_reference :groups, :admin, foreign_key: true
end
end
Mind you, I think the way you did the rename, it probably changes the index and foreign key as well. So you probably don't need to change it.
Then in your group.rb model you need to change the association:
belongs_to :admin, class_name: "User", foreign_key: :admin_id
Then if you go back to your seeds, you can change your group creation to this:
Group.new(name: "Test Group", description: "blablabal", admin: user)
Ruby on Rails has certain naming conventions for associations between models. I suggest reading about Active Record Associations
in the official Rais Guides.
For example, if there is a belongs_to :user association in a Group model then Rails expects that there is a user_id column on the groups table in the database pointing to the id of a record in a table named users.
This allows Ruby on Rails to work without much configuration and it is called convention over configuration.
If you do not want to follow these conventions then you need to configure everything that doesn't follow this convention. In your example, you would need to add foreign_key: 'admin_id' to both sides of the association to tell Ruby on Rails that you do not want to use the default column naming but admin_id instead, like this:
class User < ApplicationRecord
has_many :participants, foreign_key: 'admin_id'
# ...
class Group < ApplicationRecord
belongs_to :user, foreign_key: 'admin_id'
# ...
Because of the additional configuration needed in the case of non-default naming I highly suggest not using custom table names or foreign key names. And only use them when the database schema is not under your control. Therefore I suggest reverting the renaming from user_id to admin_id.
This question was discussed numerous times, however I run into an issue that I could not find an answer to.
I am building a login system where one Member(:class) "bails for" a new Member. Internally, they are referenced as "member" and "candidate" respectively. Until the member has accepted the bail, a BailRequest(:class) is listed in the according table.
According to the rails guide, the right way to tell rails about the class refering to would be
class BailRequest < ApplicationRecord
belongs_to :candidate, class_name: "Member"
belongs_to :member
end
where class_name: "Member" should tell rails that BailRequest.candidate is of class: Member. The very same approach worked in the member class flawlessly
class Member < ApplicationRecord
belongs_to :bail, class_name: "Member", optional: true
has_many :associates, class_name: "Member", foreign_key: "bail_id"
end
However, this time when I want to save a BailRequest to the database, I get an ActiveRecord::StatementInvalid: SQLite3::SQLException: no such table: main.candidates.
It looks like ActiveRecord is expecting a table called "candidates" here. What am I missing?
$ rails -v
Rails 5.2.1
[$ ruby -v
ruby 2.3.1p112 (2016-04-26) [i386-linux-gnu]]
The schema.rb shows the following after migration
create_table "bail_requests", force: :cascade do |t|
t.integer "candidate_id"
t.integer "member_id"
t.string "message", limit: 100
t.boolean "accepted"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["candidate_id"], name: "index_bail_requests_on_candidate_id", unique: true
t.index ["member_id"], name: "index_bail_requests_on_member_id"
end
create_table "members", force: :cascade do |t|
t.string "email", limit: 50, null: false
t.string "password_digest", null: false
t.integer "bail_id"
t.string "username", limit: 50
t.string "first_name", limit: 50
t.string "last_name", limit: 50
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_members_on_email", unique: true
t.index ["username"], name: "index_members_on_username", unique: true
end
Logic:
When a Member m registers, m has to reference a Member n by name.
m.bail equals nil/null and a BailRequest br is created with
br.member = n and br.candidate = m
If member accepts the bail, br.accepted is set to true. m.bail is set to n and m is in n.associates
In the migration file, I changed
t.references :candidate, index: {:unique=>true}
to
t.integer :candidate_id, index: {:unique=>true}, foreign_key: true
and rerun the migration. It is now working on the rails console. Thanks to arieljuod for your effort.
thinking-sphinx (3.3.0)
Rails 5.0.4
I have troubles with updating associations - ThinkingSphinx (apparently) does not reindex them.
model1
class DocRequest < ApplicationRecord
has_many :doc_responses, :inverse_of => :doc_request, dependent: :destroy
end
model2
class DocResponse < ApplicationRecord
belongs_to :doc_request, :inverse_of => :doc_responses
end
app/indices/doc_response_index.rb
ThinkingSphinx::Index.define :doc_response, :with => :active_record, :delta => true do
indexes doc_request.title, :as => :doc_request_title, :sortable => true
indexes text
has doc_request_id
end
Controller:
result = DocResponse.search #keyword
When I update the title of a DocRequest model (in Admin area), ThinkingSphinx obviously does not reindex the doc_request.title field, e.g. the result = DocResponse.search #keyword query doesn't change.
What's wrong?
Running rake ts:rebuild solves the problem. But I can't do it on every update.
I tried to add a
ThinkingSphinx::Index.define :doc_request, :with => :active_record, :delta => true do
indexes title
end
index and then to perform directly result = DocRequest.search #keyword - it worked correctly. But that's surely not what I need.
db/schema.rb
create_table "doc_requests", force: :cascade do |t|
t.string "title"
t.text "text"
t.boolean "paid"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "delta", default: true, null: false
t.index ["user_id"], name: "index_doc_requests_on_user_id", using: :btree
end
create_table "doc_responses", force: :cascade do |t|
t.boolean "chosen"
t.text "text"
t.float "price"
t.integer "user_id"
t.integer "doc_request_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "delta", default: true, null: false
t.index ["doc_request_id"], name: "index_doc_responses_on_doc_request_id", using: :btree
t.index ["user_id"], name: "index_doc_responses_on_user_id", using: :btree
end
What did I do wrong? I need some help.
Can I enable some verbose console mode in ThinkingSphinx - to see the reindexing events while updating? That might be useful for debugging.
I quess that the answer is somewhere near this http://freelancing-gods.com/thinking-sphinx/deltas.html#deltas-and-associations , but what exactly to do - I don't know.
Thinking Sphinx does not automatically detect your associations and how they relate to your indexed data whenever you make an update to your models, as that could potentially be very complex (and thus, slow).
You've linked to the exactly correct part of the documentation, though it's slightly out of date - you'll want to use after_commit instead. Whenever you have a DocRequest updating, you'll want to set the delta flags for all of the associated DocResponse objects:
class DocRequest < ActiveRecord::Base
# ...
after_commit :set_response_delta_flags
# ...
private
def set_response_delta_flags
doc_responses.each { |response|
response.update_attributes :delta => true
}
end
# ...
end
Updating each response will fire off the delta processing for the affected response objects, which will in turn ensure their Sphinx data is up-to-date.
I would like an Order object to be comprised of many Product objects, so I set up a HABTM relationship on object.
I'm wondering if it's "correct" (or the Ruby/Rails) way to also include additional data within the HABTM table. For instance, if I need to compute the subtotal and there's a chance the line-item totals might need to be overridden, do I store that as part of the association table, or do I need a LineItem object or something better?
Thanks
ActiveRecord::Schema.define(version: 3) do
create_table "orders", force: true do |t|
t.string "order_id", null: false
t.string "order_status", default: "new"
# <snip>
t.decimal "pay_total", precision: 8, scale: 2, null: false
end
add_index "orders", ["order_id"], name: "index_orders_on_order_id", unique: true, using: :btree
add_index "orders", ["order_status"], name: "index_orders_on_order_status", using: :btree
create_table "orders_products", id: false, force: true do |t|
t.integer "order_id" # migrated with belongs_to
t.integer "product_id" # migrated with belongs_to
t.decimal "pay_cost", precision: 8, scale: 2, null: false
t.decimal "pay_discount", precision: 8, scale: 2, default: 0.0
t.decimal "pay_linetotal", precision: 8, scale: 2, null: false
end
add_index "orders_products", ["order_id", "product_id"], name: "index_orders_products_on_order_id_and_product_id", unique: true, using: :btree
create_table "products", force: true do |t|
t.string "name", null: false
t.decimal "price", precision: 8, scale: 2,null: false
t.boolean "active", default: true
end
Join tables (aka HABTM) are purely for joining relationships and Rails (Active Record) ignores any additional fields. However, you can get around this by using a has_many through relationship, which would make sense to call "LineItem" instead of "OrdersProducts".
class Order
has_many :line_items
has_many :products, through: :line_items
end
class LineItem
belongs_to :order
belongs_to :product
end
class Product
has_many :line_items
has_many :orders, through: :line_items
end
Not the value, the name of the attribute. Yes, for real. I don't know what the hell is going on.
The migration:
class CreateFolders < ActiveRecord::Migration
def change
create_table :folders do |t|
t.string :name, null: false
t.timestamps
end
change_table :bookmarks do |t|
t.belongs_to :folder
end
end
end
The Schema:
ActiveRecord::Schema.define(version: 20140424065045) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "bookmarks", force: true do |t|
t.string "name", null: false
t.string "url", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "folder_id"
end
create_table "folders", force: true do |t|
t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
end
What it shows inside of rails c:
[3] pry(main)> Bookmark
=> Bookmark(id: integer, name: string, url: string, created_at: datetime, updated_at: datetime, folder_id: integer)
And now, our huge glaring problem:
[3] pry(#<RSpec::Core::ExampleGroup::Nested_2::Nested_1>)> Bookmark
=> Bookmark(id: integer, name: string, url: string, created_at: datetime, updated_at: datetime, folders_id: integer)
Notice the name of the last attribute there: folders_id
Does anyone know what in the hell could ever cause this?
Finally found what the issue was, and damn is it bizarre.
So brand new in Rails 4, is ActiveRecord::Migration.maintain_test_schema!. This convenient little tool is pretty nice, however it only updates the test schema on creation of a new migration. In the process, if you get a migration wrong the first time, and update it later, you'll find inconsistencies like this.
To fix the problem, run rake db:test:prepare. You'll get a deprecation warning, but ignore it. When you check inside of rspec again it should work fine.