Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ActiveRecord::RecordInvalid: Validation failed: Technologies portfolio must exist

I was trying to save nested attributes for technologies with portfolio, I have the following code: portfolio.rb (model)

class Portfolio < ApplicationRecord
  has_many :technologies
  # do not accept the insertion if name is blank
  accepts_nested_attributes_for :technologies,
                                 reject_if: lambda{ |attrs| attrs['name'].blank? }
  validates_presence_of :title, :body, :main_image, :thumb_image
  include Placeholder
  validates_presence_of :title, :body, :main_image, :thumb_image

  # class method - custom scope
  def self.angulars
    where(subtitle: "Angular")
  end 

  # lambda - custom scope
  scope :ruby_on_rails_p_items, -> { where(subtitle: "Ruby on Rails") }

  # callbackss
  after_initialize :set_defaults

  def set_defaults
    self.main_image ||= Placeholder.image_generator(height:'600', width:'400')
    self.thumb_image ||= Placeholder.image_generator(height:'350', width:'200')
  end 
end

technology.rb model:

class Technology < ApplicationRecord
  belongs_to :portfolio
end

Now when I go to the rails console and try to insert multiple attributes:

Portfolio.create!(title: "Web App", subtitle: "asadasd", body: "sadsadas", technologies_attributes: [{name: "Ruby"}, {name: "Rails"}, {name: "Angula"}, {name: "Ionic"}])

It gave me this error:

ActiveRecord::RecordInvalid: Validation failed: Technologies portfolio must exist
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/validations.rb:78:in `raise_validation_error'

Any idea what am I missing here?

Schema for both technology and portfolio:

  create_table "portfolios", force: :cascade do |t|
    t.string   "title"
    t.string   "subtitle"
    t.text     "body"
    t.text     "main_image"
    t.text     "thumb_image"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
  end

  create_table "technologies", force: :cascade do |t|
    t.string   "name"
    t.integer  "portfolio_id"
    t.datetime "created_at",   null: false
    t.datetime "updated_at",   null: false
    t.index ["portfolio_id"], name: "index_technologies_on_portfolio_id", using: :btree
  end

COMPLETE ERROR MESSAGE:

ActiveRecord::RecordInvalid: Validation failed: Technologies portfolio must exist
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/validations.rb:78:in `raise_validation_error'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/validations.rb:50:in `save!'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/attribute_methods/dirty.rb:30:in `save!'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/transactions.rb:324:in `block in save!'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/transactions.rb:395:in `block in with_transaction_returning_status'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/connection_adapters/abstract/database_statements.rb:232:in `block in transaction'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/connection_adapters/abstract/transaction.rb:189:in `within_new_transaction'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/connection_adapters/abstract/database_statements.rb:232:in `transaction'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/transactions.rb:211:in `transaction'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/transactions.rb:392:in `with_transaction_returning_status'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/transactions.rb:324:in `save!'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/suppressor.rb:45:in `save!'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.7/lib/active_record/persistence.rb:51:in `create!'
    from (irb):3
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/railties-5.0.7/lib/rails/commands/console.rb:65:in `start'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/railties-5.0.7/lib/rails/commands/console_helper.rb:9:in `start'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/railties-5.0.7/lib/rails/commands/commands_tasks.rb:78:in `console'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/railties-5.0.7/lib/rails/commands/commands_tasks.rb:49:in `run_command!'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/railties-5.0.7/lib/rails/commands.rb:18:in `<top (required)>'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activesupport-5.0.7/lib/active_support/dependencies.rb:293:in `require'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activesupport-5.0.7/lib/active_support/dependencies.rb:293:in `block in require'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activesupport-5.0.7/lib/active_support/dependencies.rb:259:in `load_dependency'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activesupport-5.0.7/lib/active_support/dependencies.rb:293:in `require'
    from /Users/mac/Desktop/DevCampPortfolio/bin/rails:9:in `<top (required)>'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activesupport-5.0.7/lib/active_support/dependencies.rb:287:in `load'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activesupport-5.0.7/lib/active_support/dependencies.rb:287:in `block in load'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activesupport-5.0.7/lib/active_support/dependencies.rb:259:in `load_dependency'
    from /Users/mac/.rvm/gems/ruby-2.3.1/gems/activesupport-5.0.7/lib/active_support/dependencies.rb:287:in `load'
    from /Users/mac/.rvm/rubies/ruby-2.3.1/lib/ruby/site_ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /Users/mac/.rvm/rubies/ruby-2.3.1/lib/ruby/site_ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from -e:1:in `<main>'
2.3.1 :004 > Portfolio.create!(title: "Web App", subtitle: "asadasd", body: "sadsadas", main_image: 'some value', thumb_image: 'some image')

2 Answers

Making the belongs_to :portfolio association optional is not solving the problem. All you are doing by passing in optional: true is skipping validation, which is not useful if you actually want to set up a database schema with Model associations.

The underlying cause of the problem is that Active Record is attempting to create a Technology object before the Portfolio object has been committed to the database. accepts_nested_attributes_for is special in that it creates an instance of the associated model (Technology in this case) through the parent model (Portfolio). To do this Active Record needs to know about the relation between the two models, and it must also have a foreign key to tie the associated Technology object to a specific Portfolio.

If you were to create a Portfolio object first, and then a Technology object separately you wouldn't have a problem:

portfolio = Portfolio.create!(title: "Web App", subtitle: "asadasd", body: "sadsadas")

Technology.create!(name: 'aoeifjeao', portfolio_id: portfolio.id)
=> #<Technology id: 10, name: "aoeifjeao", portfolio_id: 17, created_at: "2019-12-07 03:54:47", updated_at: "2019-12-07 03:54:47"> 

The reason this works is clear if you inspect the SQL queries generated by the Technology.create code:

(0.4ms)  BEGIN
Portfolio Load (0.7ms)  SELECT  "portfolios".* FROM "portfolios" WHERE "portfolios"."id" = $1 LIMIT $2  [["id", 17], ["LIMIT", 1]]
SQL (1.6ms)  INSERT INTO "technologies" ("name", "portfolio_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["name", "aoeifjeao"], ["portfolio_id", 17], ["created_at", "2019-12-07 03:54:47.668662"], ["updated_at", "2019-12-07 03:54:47.668662"]]
(41.0ms)  COMMIT

Note that the first thing that happens is Active Record looks for a Portfolio from the database with a specific id. It then inserts a new record into the technologies table with the same portfolio_id.

This is what happens when you create a model with an association using Active Record. It looks for the parent model in the database first using the id you specified, and then that becomes the foreign key of the child model.

Returning to your code, when you call Portfolio.create, what you are actually trying to do is create both the parent and child objects, plus set up their association, all before any records are saved to the database.

What is actually happening is that Portfolio.create gets broken up into two steps. First Portfolio.new is called, which instantiates (but doesn't save) the Portfolio Object:

portfolio = Portfolio.new(title: "Web App", subtitle: "asadasd", body: "sadsadas", technologies_attributes: [{name: "Ruby"}])
=> #<Portfolio id: nil, title: "Web App", subtitle: "asadasd", body: "sadsadas", main_image: "http://placehold.it/600x400", thumb_image: "http://placehold.it/350x200", created_at: nil, updated_at: nil> 

Note that our nested attributes have already been used to instantiate a Technology Object through the Portfolio association, but the foreign key portofolio_id is nil:

portfolio.technologies
=> #<ActiveRecord::Associations::CollectionProxy [#<Technology id: nil, name: "Ruby", portfolio_id: nil, created_at: nil, updated_at: nil>]> 

Next, Portfolio.save is called, which throws ActiveRecord::RecordInvalid validation error. This is telling you that the Technology object requires a Portfolio association, which it does not have because portfolio_id is still nil.

When setting up belongs_to and has_many relationships, Active Record attempts to automatically guess the inverse association based on heuristics, typically by looking at the table names. It's usually pretty good at it, but sometimes you need to be explicit and declare the inverse_of relationships between the two models.

# portfolio.rb

class Portfolio < ApplicationRecord
  has_many :technologies, inverse_of: :portfolio
end
# technology.rb

class Technology < ApplicationRecord
  belongs_to :portfolio, inverse_of: :technologies
end
like image 75
Josh Teperman Avatar answered Dec 19 '25 22:12

Josh Teperman


You have validation on Portfolio

validates_presence_of :title, :body, :main_image, :thumb_image

And you are passing only :title, :subtile, :body but not :main_image, :thumb_image

Portfolio.create!(title: "Web App", subtitle: "asadasd", body: "sadsadas", technologies_attributes: [{name: "Ruby"}, {name: "Rails"}, {name: "Angula"}, {name: "Ionic"}])

Above code expecting saving of Portfolio should be successful, so it can use portfolio_id in Technology, but it isn't happening here you need to pass in all required parameter to Portfolio.create!

Portfolio.create!(title: "Web App", subtitle: "asadasd", body: "sadsadas", main_image: 'some value', thumb_image: 'some image',
  technologies_attributes: [{name: "Ruby"}, {name: "Rails"}, {name: "Angula"}, {name: "Ionic"}])

Update:

As you are using Rails 5.x, In Rails 5.x (onwards) all belongs_to association validates(belongs_to association) presence true by default, add optional: true to your Technology Model. Read more about this

class Technology < ApplicationRecord 
  belongs_to :portfolio, optional: true 
end
like image 45
Pramod Shinde Avatar answered Dec 19 '25 21:12

Pramod Shinde



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!