I understand the cause of a Stack Level Too Deep error. I am failing to spot where/why it is occurring in my code base.
I've implemented a multi-model, multi-step wizard. The first two models (User and Company) are working, it is when I attempt to add in the third (Address) I get the error.
I suspect the error is related to the associations between the models, although I've failed to debug.
The code snippets below function correctly except when I add the 3 lines (marked with comments in the snippet) too the file app/wizards/user_wizard/step1.rb.
Relevant Models
app/models/company.rb
class Company < ActiveRecord::Base
include Validatable::Company
# Associations:
has_many :addresses, inverse_of: :company
accepts_nested_attributes_for :addresses, reject_if: :all_blank
has_many :employees, inverse_of: :company
accepts_nested_attributes_for :employees, reject_if: :all_blank
has_many :licenses, inverse_of: :company
accepts_nested_attributes_for :licenses, reject_if: :all_blank
has_many :vehicles, inverse_of: :company
accepts_nested_attributes_for :vehicles, reject_if: :all_blank
has_one :user, inverse_of: :company
end
app/models/address.rb
class Address < ActiveRecord::Base
# Associations:
belongs_to :company, inverse_of: :addresses
has_many :licenses, inverse_of: :address
accepts_nested_attributes_for :licenses, reject_if: :all_blank
has_many :initial_analyses, inverse_of: :address
accepts_nested_attributes_for :initial_analyses, reject_if: :all_blank
end
app/models/user.rb
class User < ActiveRecord::Base
include SoftDeletable
include Validatable::User
# Constants:
MARKER_ATTRIBUTES = %w[user_name].freeze # get marked with '(deleted)'
DEPENDANT_CHILDREN = %w[none].freeze # child resources to be deleted
# Associations:
belongs_to :role, inverse_of: :users
belongs_to :company, inverse_of: :user
accepts_nested_attributes_for :company, reject_if: :all_blank
has_many :auto_quotes, inverse_of: :user
end
db/schema.rb
ActiveRecord::Schema.define(version: 20170616131833) do
create_table "addresses", force: :cascade do |t|
t.integer "company_id"
t.text "site_name"
t.string "premises_code"
t.string "exempt_premises_code"
t.text "address"
t.string "city"
t.string "county"
t.string "sic_code"
t.string "postcode"
t.string "country"
t.boolean "sic_update"
t.boolean "deleted", default: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "deleted_at"
end
create_table "companies", force: :cascade do |t|
t.string "company_name"
t.string "registration_number"
t.string "type_of_business"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "deleted_at"
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.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "user_name"
t.datetime "deleted_at"
t.integer "role_id"
t.integer "company_id"
t.string "invitation_token"
t.datetime "invitation_created_at"
t.datetime "invitation_sent_at"
t.datetime "invitation_accepted_at"
t.integer "invitation_limit"
t.integer "invited_by_id"
t.string "invited_by_type"
t.integer "invitations_count", default: 0
end
add_index "users", ["company_id"], name: "index_users_on_company_id", unique: true
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["invitation_token"], name: "index_users_on_invitation_token", unique: true
add_index "users", ["invitations_count"], name: "index_users_on_invitations_count"
add_index "users", ["invited_by_id"], name: "index_users_on_invited_by_id"
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
add_index "users", ["unlock_token"], name: "index_users_on_unlock_token", unique: true
end
Wizards
app/wizards/user_wizard/base.rb
module UserWizard
class Base
include ActiveModel::Model
STEPS = %w[step1 step2].freeze
attr_accessor :user
delegate(*::User.attribute_names.map {|attr| [attr, "#{attr}="] }.flatten, to: :user)
def initialize(user_attributes)
#user = ::User.new(user_attributes)
end
end
end
app/wizards/user_wizard/step1.rb
module UserWizard
class Step1 < UserWizard::Base
include Validatable::Company
attr_accessor :company
# One of 3 lines triggering circular reference by adding in Address model
attr_accessor :address
delegate(*::Company.attribute_names.map {|attr| [attr, "#{attr}="] }.flatten, to: :company)
# One of 3 lines triggering circular reference by adding in Address model
delegate(*::Address.attribute_names.map {|attr| [attr, "#{attr}="] }.flatten, to: :address)
def initialize(user_attributes)
super
#company = #user.build_company
# One of 3 lines triggering circular reference by adding in Address model
#address = #user.company.addresses.build
end
end
end
app/wizards/user_wizard/step2.rb
ommitted as it is irrelevant. code fails before ever instantiating this class
address has an address attribute. So the delegate method is trying to create a method address that will be delegated to address.
I'd suggest this:
module UserWizard
class Step1 < UserWizard::Base
include Validatable::Company
attr_accessor :company
# One of 3 lines triggering circular reference by adding in Address model
attr_accessor :company_address
delegate(*::Company.attribute_names.map {|attr| [attr, "#{attr}="] }.flatten, to: :company)
# One of 3 lines triggering circular reference by adding in Address model
delegate(*::Address.attribute_names.map {|attr| [attr, "#{attr}="] }.flatten, to: :company_address)
def initialize(user_attributes)
super
#company = #user.build_company
# One of 3 lines triggering circular reference by adding in Address model
#company_address = #user.company.addresses.build
end
end
end
Related
Server log screencap
Hi everyone!
Was writing a rating system for an airBnb style project so i made the objects Host and Guest as reference to the User object.
But something is wrong in my code:
SQLite3::SQLException: no such table: main.hosts
In fact the method looks for host table i dont have cause it should be associated to the Users one.
migration
class CreateReviews < ActiveRecord::Migration[6.0]
def change
create_table :reviews do |t|
t.text :comment
t.integer :star, default: 1
t.references :car, foreign_key: true
t.references :reservation, foreign_key: true
t.references :guest, foreign_key: true
t.references :host, foreign_key: true
t.string :type
t.timestamps
end
end
end
Schema
create_table "reviews", force: :cascade do |t|
t.text "comment"
t.integer "star", default: 1
t.integer "car_id"
t.integer "reservation_id"
t.integer "guest_id"
t.integer "host_id"
t.string "type"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["car_id"], name: "index_reviews_on_car_id"
t.index ["guest_id"], name: "index_reviews_on_guest_id"
t.index ["host_id"], name: "index_reviews_on_host_id"
t.index ["reservation_id"], name: "index_reviews_on_reservation_id"
Models:
class HostReview < Review
belongs_to :host, class_name: "User"
end
class User < ApplicationRecord
....
has_many :host_reviews, class_name: "HostReview", foreign_key: "host_id"
I think you can do something like this:
t.references :host, references: :users, foreign_key: true
or alternatively
t.integer :host_id
and then
add_foreign_key :reviews, :users, column: :host_id
I am trying to track GPS satellites as they cross the sky. I receive 'SKY' messages every 5 seconds, containing location and signal strength data for each satellite currently visible. The database contains tables for 'skies', 'sats', 'tracks' and 'points'. The idea is that each sky received will add a location point to each of its satellite's track. Schema is...
create_table "points", force: :cascade do |t|
t.bigint "track_id"
t.integer "az"
t.integer "el"
t.integer "ss"
t.boolean "used"
t.boolean "duplicate"
t.integer "PRN"
t.bigint "sky_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["PRN"], name: "index_points_on_PRN"
t.index ["created_at"], name: "index_points_on_created_at"
t.index ["sky_id"], name: "index_points_on_sky_id"
t.index ["track_id"], name: "index_points_on_track_id"
end
create_table "sats", force: :cascade do |t|
t.integer "PRN"
t.datetime "created_at", precision: 6
t.datetime "updated_at", precision: 6
t.index ["PRN"], name: "index_sats_on_PRN"
end
create_table "sats_skies", id: false, force: :cascade do |t|
t.bigint "sky_id", null: false
t.bigint "sat_id", null: false
t.index ["sat_id", "sky_id"], name: "index_sats_skies_on_sat_id_and_sky_id"
t.index ["sky_id", "sat_id"], name: "index_sats_skies_on_sky_id_and_sat_id"
end
create_table "skies", force: :cascade do |t|
t.string "klass"
t.string "tag"
t.string "device"
t.float "time"
t.float "xdop"
t.float "ydop"
t.float "vdop"
t.float "tdop"
t.float "hdop"
t.float "pdop"
t.float "gdop"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["created_at"], name: "index_skies_on_created_at"
end
create_table "tracks", force: :cascade do |t|
t.bigint "sat_id"
t.index ["sat_id"], name: "index_tracks_on_sat_id"
end
The model definitions are...
class Sky < ApplicationRecord
include ActionView::Helpers::NumberHelper
has_and_belongs_to_many :sats, inverse_of: :sky, dependent: :destroy
has_many :points, inverse_of: :sky
accepts_nested_attributes_for :sats, allow_destroy: true
accepts_nested_attributes_for :points, allow_destroy: true
end
class Sat < ApplicationRecord
has_and_belongs_to_many :skies, inverse_of: :sat, autosave: true
has_one :track, inverse_of: :sat
accepts_nested_attributes_for :skies, allow_destroy: true
accepts_nested_attributes_for :track, allow_destroy: true
end
class Track < ApplicationRecord
has_many :points, inverse_of: :track
belongs_to :sat, inverse_of: :track
accepts_nested_attributes_for :points, allow_destroy: true
end
class Point < ApplicationRecord
belongs_to :sky, inverse_of: :point #, optional: true
belongs_to :track, inverse_of: :point #, optional: true
end
When a sky message is received, it checks the previous sky message to see which of it's satellites also existed there. For those found, the point is just added to that satellite's track table. If the satellite was not visible in the last sky, a new sat record is created, and its location 'point' is added to the new sat's track.
Note that the point record has foreign keys for both the track, and the sky records.
Initially, I had all this working, but I was creating the sky record, then creating the sat/track records (if needed), then saving the point. A power failure while processing a sky message caused the database to contain a sat/track with no points. I realize I could just surround all this processing with a transaction block, but thought a cleaner solution would be to create all the various components for a sky message in memory and when complete save the whole batch with a single new_sky.save.
But try as I may, I don't seem to be able to get the proper combination 'reverse_of', 'nested_attributes' and other definition attributes to successfully save the results. With everything configured as listed here, I am currently getting the following while processing the first satellite in the first sky message received...
D, [2020-05-03 10:37:32.410281000 -0500 CDT#83744] DEBUG -- GpsComms: ************* This sat (#8) not found in previous SKY, create new *************
E, [2020-05-03 10:37:32.436055000 -0500 CDT#83744] ERROR -- GpsComms: Sat Processing Failed
E, [2020-05-03 10:37:32.436454000 -0500 CDT#83744] ERROR -- GpsComms: {:Rescue=>"#<ActiveRecord::InverseOfAssociationNotFoundError: Could not find the inverse association for track (:point in Track)>"}
E, [2020-05-03 10:37:32.436786000 -0500 CDT#83744] ERROR -- GpsComms: Could not find the inverse association for track (:point in Track) - (ActiveRecord::InverseOfAssociationNotFoundError)
With other combinations of options (reverse_of:, optional:, etc) I get error messages stating various object must exist.
I think I'm close. I'm hoping somebody can figure out the correct combination of definitions to get this to work.
Thanks for any help,
UPDATE:
I believed that the error above ("Could not find the inverse association for track (:point in Track)") was being generated when attempting to save the new sky object. It is actually being generated when the new point is being added to the points collection.
At this point I have created the new sky with
sky=Sky.new( parameters)
The satellite has been created with
sat=sky.sats.build(parameters)
The track with
track=sat.build_track(empty)
Now I can do either
new_point=sat.track.points.build(parameters)
or
sat.track.points << Point.new(parameters)
to generate the inverse assoc error.
Hope this helps!
UPDATE:
Thanks to the suggestion from Eyselandic below -- here is a run-able file. It errors out on the last line with the 'inverse association' error.
# Activate the gem you are reporting the issue against.
gem 'activerecord', '6.0.0'
require 'active_record'
require 'minitest/autorun'
require 'logger'
# Ensure backward compatibility with Minitest 4
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(
adapter: 'postgresql',
database: 'database_name',
username: 'username',
password: 'password'
)
ActiveRecord::Base.logger = Logger.new(STDOUT)
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
ActiveRecord::Schema.define do
create_table "points", force: :cascade do |t|
t.bigint "track_id"
t.integer "az"
t.integer "el"
t.integer "ss"
t.boolean "used"
t.boolean "duplicate"
t.integer "PRN"
t.bigint "sky_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["PRN"], name: "index_points_on_PRN"
t.index ["created_at"], name: "index_points_on_created_at"
t.index ["sky_id"], name: "index_points_on_sky_id"
t.index ["track_id"], name: "index_points_on_track_id"
end
create_table "sats", force: :cascade do |t|
t.integer "PRN"
t.datetime "created_at", precision: 6
t.datetime "updated_at", precision: 6
t.index ["PRN"], name: "index_sats_on_PRN"
end
create_table "sats_skies", id: false, force: :cascade do |t|
t.bigint "sky_id", null: false
t.bigint "sat_id", null: false
t.index ["sat_id", "sky_id"], name: "index_sats_skies_on_sat_id_and_sky_id"
t.index ["sky_id", "sat_id"], name: "index_sats_skies_on_sky_id_and_sat_id"
end
create_table "skies", force: :cascade do |t|
t.string "klass"
t.string "tag"
t.string "device"
t.float "time"
t.float "xdop"
t.float "ydop"
t.float "vdop"
t.float "tdop"
t.float "hdop"
t.float "pdop"
t.float "gdop"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["created_at"], name: "index_skies_on_created_at"
end
create_table "tracks", force: :cascade do |t|
t.bigint "sat_id"
t.index ["sat_id"], name: "index_tracks_on_sat_id"
end
end
class Sky < ApplicationRecord
has_and_belongs_to_many :sats, inverse_of: :sky, dependent: :destroy
has_many :points, inverse_of: :sky
accepts_nested_attributes_for :sats, allow_destroy: true
accepts_nested_attributes_for :points, allow_destroy: true
end
class Sat < ApplicationRecord
has_and_belongs_to_many :skies, inverse_of: :sat, autosave: true
has_one :track, inverse_of: :sat
accepts_nested_attributes_for :skies, allow_destroy: true
accepts_nested_attributes_for :track, allow_destroy: true
end
class Track < ApplicationRecord
has_many :points, inverse_of: :track
belongs_to :sat, inverse_of: :track
accepts_nested_attributes_for :points, allow_destroy: true
end
class Point < ApplicationRecord
belongs_to :sky, inverse_of: :point #, optional: true
belongs_to :track, inverse_of: :point #, optional: true
end
class BugTest < Minitest::Test
def test_association_stuff
sky_attr= {
:klass=>"SKY",
:device=>"/dev/ttyAMA0",
:xdop=>0.62,
:ydop=>0.89,
:vdop=>0.94,
:tdop=>1.15,
:hdop=>1.23,
:gdop=>2.28,
:pdop=>1.55
}
sky = Sky.new(sky_attr)
sat = sky.sats.build(:PRN=>7)
track = sat.build_track
point_attr= {
:PRN=>7,
:el=>61,
:az=>340,
:ss=>21,
:used=>true,
# :sky_id=>nil,
:duplicate=>false
}
point=sat.track.points.build( point_attr)
sky.save
end
end
Error:
Error:
BugTest#test_association_stuff:
ActiveRecord::InverseOfAssociationNotFoundError: Could not find the inverse association for track (:point in Track)
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/reflection.rb:240:in `check_validity_of_inverse!'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/reflection.rb:474:in `check_validity!'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:43:in `initialize'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations.rb:237:in `new'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations.rb:237:in `association'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:286:in `inverse_association_for'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:107:in `set_inverse_instance'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:187:in `initialize_attributes'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:318:in `block in build_record'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/core.rb:328:in `initialize'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/inheritance.rb:70:in `new'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/inheritance.rb:70:in `new'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/reflection.rb:158:in `build_association'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:317:in `build_record'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/collection_association.rb:108:in `build'
/Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/collection_proxy.rb:316:in `build'
active_record_gem.rb:136:in `test_association_stuff'
Before I start, I should say I have already checked out Rails 5.1.: destroy records in "has_many: through" association with restriction and has_many through association dependent destroy under condition of who called destroy without results.
My app consist in USERS that has_one EMPRESA.
An EMPRESA may have several TAGS
A TAG may have several EMPRESAS (To do this I have used has_many :through)
My case: I got this screen error:
And I know the origin of this error is because I'm trying to destroy items with pending references. But I can't identify th issue.
By looking at server console I can guess the problem involve empresa, tag, and tagging.
Models involved
class Empresa < ApplicationRecord
skip_callback :validate, after: :create
after_initialize :set_default_plan, :if => :new_record?
attr_accessor :tag_list
enum plan: [:noplan, :basic, :plus, :premium]
belongs_to :user
belongs_to :category, optional: true
has_many :promos, dependent: :destroy
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
mount_uploader :logo, LogoUploader
mount_uploaders :fotos, FotosUploader
def tag_list
tags.join(", ")
end
def tag_list=(names)
tag_names = names.split(",").collect {|str| str.strip.downcase}.uniq
new_or_existing_tags = tag_names.collect {|tag_name| Tag.find_or_create_by(name: tag_name)}
self.tags = new_or_existing_tags
end
def set_default_plan
self.plan ||= :noplan
end
end
class Tag < ApplicationRecord
has_many :empresas, through: :taggings
has_many :taggings, dependent: :destroy
def to_s
name
end
end
class Tagging < ApplicationRecord
belongs_to :empresa
belongs_to :tag
end
class Category < ApplicationRecord
validates :name, presence: true, length:{ minimum: 3 }, uniqueness: true
has_many :empresas, dependent: :nullify
end
class User < ApplicationRecord
enum role: [:user, :editor, :admin, :superadmin]
after_initialize :set_default_role, :if => :new_record?
has_one :empresa, dependent: :destroy
has_many :incidents, dependent: :destroy
has_many :comments, dependent: :destroy
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
def set_default_role
self.role ||= :user
end
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
schema.rb (irrelevant tables removed)
ActiveRecord::Schema.define(version: 20180531033550) do
enable_extension "plpgsql"
create_table "empresas", force: :cascade do |t|
t.string "logo"
t.string "name"
t.text "description"
t.text "excerpt"
t.string "address"
t.string "web"
t.string "email"
t.string "tel"
t.string "video"
t.json "fotos"
t.integer "plan", default: 0
t.float "mlon"
t.float "mlat"
t.string "schedule0"
t.string "schedule1"
t.string "schedule2"
t.string "schedule3"
t.string "schedule4"
t.string "schedule5"
t.string "schedule6"
t.string "schedule7"
t.string "schedule8"
t.string "schedule9"
t.string "schedule10"
t.string "schedule11"
t.string "schedule12"
t.string "schedule13"
t.string "schedule14"
t.string "schedule15"
t.string "schedule16"
t.string "schedule17"
t.string "schedule18"
t.string "schedule19"
t.string "schedule20"
t.string "schedule21"
t.string "schedule22"
t.string "schedule23"
t.string "schedule24"
t.string "schedule25"
t.string "schedule26"
t.string "schedule27"
t.integer "tag_id"
t.integer "offer_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "category_id"
t.index ["category_id"], name: "index_empresas_on_category_id", using: :btree
end
create_table "taggings", force: :cascade do |t|
t.integer "empresa_id"
t.integer "tag_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["empresa_id"], name: "index_taggings_on_empresa_id", using: :btree
t.index ["tag_id"], name: "index_taggings_on_tag_id", using: :btree
end
create_table "tags", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
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.integer "creditos", default: 0, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "empresa_id"
t.integer "role"
t.string "first_name"
t.string "last_name"
t.date "birthdate"
t.string "dni"
t.string "phone"
t.string "address"
t.string "gender"
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
t.index ["empresa_id"], name: "index_users_on_empresa_id", using: :btree
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
end
add_foreign_key "comments", "incidents"
add_foreign_key "comments", "users"
add_foreign_key "incidents", "users"
add_foreign_key "promos", "empresas"
add_foreign_key "taggings", "empresas"
add_foreign_key "taggings", "tags"
add_foreign_key "users", "empresas"
end
You do not need empresa_id in users.
Remove the referential integrity and column via:
rails g migration remove_constraint_from_users
Edit the newly created migration file to add the following in def change block
def change
remove_foreign_key :users, :empresas
remove_column :users, :empresa_id
end
I have multiple user types defined in a single user model:
enum role: { staff: 0, clinician: 1, admin: 2 }
Clinician users each have a clinician profile that is automatically created using after_create, and I'm intending to store the clinician_profile id on the users table. For some reason, when the clinician profiles are created, the clinician_profile_id remains null on the users table for all users, including clinician users. How do I fix that?
module ClinicianUser
extend ActiveSupport::Concern
included do
belongs_to :clinician_profile
has_many :lists
has_many :universities, through: :lists
has_many :dispatches
has_many :referral_requests, through: :dispatches
after_create :create_clinician_profile, if: :clinician?
belongs_to :market
validates :market_id, presence: true, if: :clinician?
end
class_methods do
def create_clinician_profile
self.clinician_profile.create!
end
end
end
class ClinicianProfile < ApplicationRecord
has_one :user, -> { where role: :clinician }
end
Users table schema:
create_table "users", force: :cascade do |t|
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.string "remember_digest"
t.string "activation_digest"
t.boolean "activated", default: false
t.datetime "activated_at"
t.string "reset_digest"
t.datetime "reset_sent_at"
t.string "encrypted_password", limit: 128
t.string "confirmation_token", limit: 128
t.string "remember_token", limit: 128
t.datetime "confirmed_at"
t.integer "role", default: 0
t.string "first_name"
t.string "last_name"
t.integer "university_id"
t.boolean "approved", default: false
t.integer "market_id"
t.integer "clinician_profile_id"
t.index ["clinician_profile_id"], name: "index_users_on_clinician_profile_id"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["market_id"], name: "index_users_on_market_id"
t.index ["remember_token"], name: "index_users_on_remember_token"
A class method shouldn't be used here to operate on a instance object.
You could use a callback block instead:
after_create do |user|
ClinicianProfile.create(user: user) if clinician?
end
Furthermore the association is defined as a belong_to so the parent object can create the association too but that's just a personal opinion.
I have been strugling on this issue for 4 days and I am wondering whether I am not facing an ActiveRecord bug? I am trying to link a User model to a Callout model.
user.rb
class User < ActiveRecord::Base
has_many :callouts_users
has_many :callouts, through: :callouts_users
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
has_many :posts, inverse_of: :creator
has_many :callouts, as: :calloutable
has_many :profiles, as: :profileable, :validate => true
validates :name, presence: true
validates_uniqueness_of :name, :case_sensitive => false, :message => "This name has already been taken"
end
callout.rb
class Callout < ActiveRecord::Base
has_many :callouts_users
has_many :users, through: :callouts_users
belongs_to :conversation
belongs_to :calloutable, polymorphic: true, class_name: "::Callout", :validate => true
validates :conversation, presence: true
validates :calloutable, presence: true
validates_uniqueness_of :calloutable_id, :scope => [:user_id, :conversation_id, :calloutable_type]
end
user_callout.rb
class UserCallout < ActiveRecord::Base
belongs_to :user
belongs_to :callout
validates :type, presence: true,
validates :type, :inclusion=> { :in => ["up", "down"] }
end
My migrations are as follows:
..._create_callouts_users.rb
class CreateCalloutsUsers < ActiveRecord::Migration
def change
create_table :callouts_users do |t|
t.timestamps null: false
end
end
end
..._add_callout_to_callouts_users.rb
class AddCalloutToCalloutsUsers < ActiveRecord::Migration
def change
add_reference :callouts_users, :callout, index: true
add_foreign_key :callouts_users, :callouts
end
end
..._add_user_to_callouts_users.rb
class AddUserToCalloutsUsers < ActiveRecord::Migration
def change
add_reference :callouts_users, :user, index: true
add_foreign_key :callouts_users, :users
end
end
and when I try to do something like
#callout = #conversation.callouts.find_by(calloutable: #user)
if(#callout.nil?) #callout = Callout.new(conversation: #conversation, calloutable: #user)
#callout.users << current_user
#callout.save
I immediately have:
ActiveRecord::StatementInvalid in CalloutsController#create
SQLite3::SQLException: no such column: callouts.user_id: SELECT 1 AS one FROM "callouts" WHERE ("callouts"."calloutable_id" IS NULL AND "callouts"."user_id" IS NULL AND "callouts"."conversation_id" IS NULL AND "callouts"."calloutable_type" IS NULL) LIMIT 1
So as if ActiverRecords where looking for a "user_id" column on my callouts table while the user_id is only on the join table side...
I am doing something wrong on my model? Why is my has_many - trough association not recogognized?
Here is the SQL code generated:
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE (LOWER("users"."name") = LOWER('name10') AND "users"."id" != 10) LIMIT 1
Callout Exists (0.6ms) SELECT 1 AS one FROM "callouts" WHERE ("callouts"."calloutable_id" IS NULL AND "callouts"."user_id" IS NULL AND "callouts"."conversation_id" = 1 AND "callouts"."calloutable_type" IS NULL) LIMIT 1
SQLite3::SQLException: no such column: callouts.user_id: SELECT 1 AS one FROM "callouts" WHERE ("callouts"."calloutable_id" IS NULL AND "callouts"."user_id" IS NULL AND "callouts"."conversation_id" = 1 AND "callouts"."calloutable_type" IS NULL) LIMIT 1
(0.0ms) rollback transaction
Completed 500 Internal Server Error in 50ms
ActiveRecord::Schema.define(version: 20150720002524) do
create_table "callouts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "conversation_id"
t.integer "calloutable_id"
t.string "calloutable_type"
end
add_index "callouts", ["calloutable_type", "calloutable_id"], name: "index_callouts_on_calloutable_type_and_calloutable_id"
add_index "callouts", ["conversation_id"], name: "index_callouts_on_conversation_id"
create_table "callouts_users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "callout_id"
end
add_index "callouts_users", ["callout_id"], name: "index_callouts_users_on_callout_id"
add_index "callouts_users", ["user_id"], name: "index_callouts_users_on_user_id"
create_table "conversations", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "conversation_id"
t.integer "creator_id"
t.text "title"
t.text "content"
end
add_index "posts", ["conversation_id"], name: "index_posts_on_conversation_id"
add_index "posts", ["creator_id"], name: "index_posts_on_creator_id"
create_table "potential_users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "profiles", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "profileable_id"
t.string "profileable_type"
t.string "description"
end
add_index "profiles", ["description"], name: "index_profiles_on_description", unique: true
add_index "profiles", ["profileable_type", "profileable_id"], name: "index_profiles_on_profileable_type_and_profileable_id"
create_table "users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
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.string "name"
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
===================================================>
I have been strugling on this issue for 4 days and I am wondering whether I am not facing an ActiveRecord bug? I am trying to link a User model to a Callout model.
user.rb
class User < ActiveRecord::Base
has_many :callouts_users
has_many :callouts, through: :callouts_users
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
has_many :posts, inverse_of: :creator
has_many :callouts, as: :calloutable
has_many :profiles, as: :profileable, :validate => true
validates :name, presence: true
validates_uniqueness_of :name, :case_sensitive => false, :message => "This name has already been taken"
end
callout.rb
class Callout < ActiveRecord::Base
has_many :callouts_users
has_many :users, through: :callouts_users
belongs_to :conversation
belongs_to :calloutable, polymorphic: true, class_name: "::Callout", :validate => true
validates :conversation, presence: true
validates :calloutable, presence: true
validates_uniqueness_of :calloutable_id, :scope => [:user_id, :conversation_id, :calloutable_type]
end
user_callout.rb
class UserCallout < ActiveRecord::Base
belongs_to :user
belongs_to :callout
validates :type, presence: true,
validates :type, :inclusion=> { :in => ["up", "down"] }
end
My migrations are as follows:
..._create_callouts_users.rb
class CreateCalloutsUsers < ActiveRecord::Migration
def change
create_table :callouts_users do |t|
t.timestamps null: false
end
end
end
..._add_callout_to_callouts_users.rb
class AddCalloutToCalloutsUsers < ActiveRecord::Migration
def change
add_reference :callouts_users, :callout, index: true
add_foreign_key :callouts_users, :callouts
end
end
..._add_user_to_callouts_users.rb
class AddUserToCalloutsUsers < ActiveRecord::Migration
def change
add_reference :callouts_users, :user, index: true
add_foreign_key :callouts_users, :users
end
end
and when I try to do something like
#callout = #conversation.callouts.find_by(calloutable: #user)
if(#callout.nil?) #callout = Callout.new(conversation: #conversation, calloutable: #user)
#callout.users << current_user
#callout.save
I immediately have:
ActiveRecord::StatementInvalid in CalloutsController#create
SQLite3::SQLException: no such column: callouts.user_id: SELECT 1 AS one FROM "callouts" WHERE ("callouts"."calloutable_id" IS NULL AND "callouts"."user_id" IS NULL AND "callouts"."conversation_id" IS NULL AND "callouts"."calloutable_type" IS NULL) LIMIT 1
So as if ActiverRecords where looking for a "user_id" column on my callouts table while the user_id is only on the join table side...
I am doing something wrong on my model? Why is my has_many - trough association not recogognized?
Here is the SQL code generated:
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE (LOWER("users"."name") = LOWER('name10') AND "users"."id" != 10) LIMIT 1
Callout Exists (0.6ms) SELECT 1 AS one FROM "callouts" WHERE ("callouts"."calloutable_id" IS NULL AND "callouts"."user_id" IS NULL AND "callouts"."conversation_id" = 1 AND "callouts"."calloutable_type" IS NULL) LIMIT 1
SQLite3::SQLException: no such column: callouts.user_id: SELECT 1 AS one FROM "callouts" WHERE ("callouts"."calloutable_id" IS NULL AND "callouts"."user_id" IS NULL AND "callouts"."conversation_id" = 1 AND "callouts"."calloutable_type" IS NULL) LIMIT 1
(0.0ms) rollback transaction
Completed 500 Internal Server Error in 50ms
ActiveRecord::Schema.define(version: 20150720002524) do
create_table "callouts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "conversation_id"
t.integer "calloutable_id"
t.string "calloutable_type"
end
add_index "callouts", ["calloutable_type", "calloutable_id"], name: "index_callouts_on_calloutable_type_and_calloutable_id"
add_index "callouts", ["conversation_id"], name: "index_callouts_on_conversation_id"
create_table "callouts_users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "callout_id"
end
add_index "callouts_users", ["callout_id"], name: "index_callouts_users_on_callout_id"
add_index "callouts_users", ["user_id"], name: "index_callouts_users_on_user_id"
create_table "conversations", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "conversation_id"
t.integer "creator_id"
t.text "title"
t.text "content"
end
add_index "posts", ["conversation_id"], name: "index_posts_on_conversation_id"
add_index "posts", ["creator_id"], name: "index_posts_on_creator_id"
create_table "potential_users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "profiles", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "profileable_id"
t.string "profileable_type"
t.string "description"
end
add_index "profiles", ["description"], name: "index_profiles_on_description", unique: true
add_index "profiles", ["profileable_type", "profileable_id"], name: "index_profiles_on_profileable_type_and_profileable_id"
create_table "users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
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.string "name"
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
=============================> EDIT
Ok,
I have another clue. I think that the error lies in the User Model but I don't know how to write it.
To make short:
1.)
Different UserS can participate in differents Callouts. So User <=> Callout is a "has_many / through" relation. (not HABTM because I need to customize the Join Model). So I can write #callout.users
2.)
One Callout targets at one Calloutable (Calloutable are either User or PotentialUser). But a Calloutable may be targeted by different Callouts. So it is a belong_to / has_many Polymorphic relation. I can write #user.callouts...and also #callout.users...
But : #callout.users in situation 1) or 2) don't mean the same thing.
Here are the detailled models:
class Callout < ActiveRecord::Base
has_many :callouts_users
has_many :users, through: :callouts_users
belongs_to :calloutable, polymorphic: true, class_name: "::Callout", :validate => true
validates :calloutable, presence: true
validates_uniqueness_of :calloutable_id, :scope => [:user_id, :conversation_id, :calloutable_type]
belongs_to :conversation
validates :conversation, presence: true
end
class CalloutsUser < ActiveRecord::Base
belongs_to :user
belongs_to :callout
validates_uniqueness_of :user_id, :scope => [:callout_id]
end
class User < ActiveRecord::Base
has_many :callouts_users
has_many :callouts, through: :callouts_users
has_many :callouts, as: :calloutable
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
has_many :posts, inverse_of: :creator
has_many :profiles, as: :profileable, :validate => true
validates :name, presence: true
validates_uniqueness_of :name, :case_sensitive => false, :message => "This name has already been taken"
end
class PotentialUser < ActiveRecord::Base
has_many :callouts, as: :calloutable
has_one :profile, as: :profileable, :validate => true
validates :profile, presence: true
end
Any idea to rewrite the User Model part ? (the 2 has_many :callouts) I think I just need to make difference to rails between #user.callouts received by the user and #user.callouts made by the user...
I feel as though you've made this harder than it needs to be. Don't fight the framework, it's here to help!
First, for your join table, use the standard Rails naming convention: order the models alphabetically. If you roll back a bit, rather than create three migrations for one action, why not:
rails g migration callouts_users callout_id:integer user_id:integer
That coupled with has_and_belongs_to_many relations in your models and you should be good to go.
It is fixed !!
the problem was in fact that I had written :
has_many :callouts_users
has_many :callouts, through: :callouts_users
has_many :callouts, as: :calloutable
So I was defining has_many :callouts, twice. And of course, Rails didn't know how to understand #callout.users
With :
has_many :callouts_users
has_many :callouts, through: :callouts_users, source: "call", class_name: "Call"
has_many :callins, as: :callable, class_name: "Call"`
I works perfectely !
Thank you for your patience and comprehension for the neewbie I am... :-)