Bazley
Bazley

Reputation: 2847

Rails running before_create for existing database record

I have a bunch of character models already in the database. Each character has_one :iconfolio. I migrated the database to add icon_url and post_url attributes to the iconfolio model, so I'm trying to update all iconfolio models in the database using the rails console.

However, setting the correct attributes for each existing record is non-trivial, so I've also added a before_create to iconfolio which will correctly set the icon_url and post_url attributes for new characters and iconfolios. But how do I force this code to execute for characters and iconfolios already in the database?

In the console I've tried:

Character.all.find_each do |char|
  char.create_iconfolio
end

but it doesn't update the database. It seems to delete the record:

(0.4ms)  COMMIT
Iconfolio Load (0.2ms)  SELECT  "iconfolios".* FROM "iconfolios" WHERE "iconfolios"."character_id" = $1 LIMIT 1  [["character_id", 46]]
(0.1ms)  BEGIN
SQL (0.2ms)  DELETE FROM "iconfolios" WHERE "iconfolios"."id" = $1  [["id", 46]]

I also tried:

Character.all.find_each do |char|
  Iconfolio.create(character_id: char.id)
end

This produced:

(0.2ms)  BEGIN
Character Load (0.6ms)  SELECT  "characters".* FROM "characters" WHERE "characters"."id" = $1 LIMIT 1  [["id", 2]]
SQL (0.5ms)  INSERT INTO "iconfolios" ("character_id", "created_at", "updated_at", "person_url", "followed_url", "icon_url", "post_url") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id"  [["character_id", 2], ["created_at", "2017-08-02 13:27:35.897842"], ["updated_at", "2017-08-02 13:27:35.897842"], ["person_url", "/assets/icon1.png"], ["followed_url", "/assets/icon2.png"], ["icon_url", "/assets/icon7.png"], ["post_url", "/assets/icon8.png"]]
(0.4ms)  COMMIT

which looks promising, but the database still not updated. (I've tried restarting the server etc).

What am I doing wrong?

iconfolio.rb

belongs_to :character

validates :character_id, presence: true

before_create do
  if self.character.type.is_a? User
    self.icon_url = '/assets/icon5.png'
    self.post_url = '/assets/icon6.png'
  else
    self.icon_url = '/assets/icon7.png'
    self.post_url = '/assets/icon8.png'
  end
end

Upvotes: 1

Views: 346

Answers (2)

MrYoshiji
MrYoshiji

Reputation: 54882

As I commented on your question, you should not use Rails' console to update the existing Data to respect the new DB structure (I can provide examples why you should not do that if you need).

You should update this data in the very same migration that changes de DB structure:

# 1234_your_migration.rb
def up
  # changing the DB structure
  add_column :iconfolios, :icon_url, :string
  add_column :iconfolios, :post_url, :string

  # updating the current data
  Iconfolio.includes(:character).find_each do |iconfolio|
    if self.character.type.is_a? User
      self.icon_url = '/assets/icon5.png'
      self.post_url = '/assets/icon6.png'
    else
      self.icon_url = '/assets/icon7.png'
      self.post_url = '/assets/icon8.png'
    end
  end
end

This might be a heavy migration to run, depending on the quantity of Iconfolio records you have in the DB. You can do as shown here OR define a method Iconfolio#update_icon_and_post_urls but this method would be used only in the context of the Iconfolio creation OR in your migration, so that might be overkill, polluting your Iconfolio model in the long term and most importantly, can lead to problems when re-running migrations (see my comment on that answer).

Upvotes: 1

MZaragoza
MZaragoza

Reputation: 10111

What I like to do is move the before_create to a method

class Iconfolio
  belongs_to :character
  validates :character_id, presence: true
  before_create :set_attributes 

  def set_attributes
    if self.character.type.is_a? User
      self.icon_url = '/assets/icon5.png'
      self.post_url = '/assets/icon6.png'
    else
      self.icon_url = '/assets/icon7.png'
      self.post_url = '/assets/icon8.png'
    end
  end
end

now you can always call it when you need to

Upvotes: 2

Related Questions