2017-08-02 31 views
1

我有一堆character模型已经在数据库中。每个character has_one :iconfolio。我迁移数据库以将icon_urlpost_url属性添加到iconfolio模型,因此我试图使用rails控制台更新数据库中的所有iconfolio模型。为现有数据库记录运行before_create的轨道

然而,对于每个现有的记录设置正确的属性是不平凡的,所以我也加入了before_createiconfolio这将正确设置新charactersiconfoliosicon_urlpost_url属性。但是,我如何强制执行此代码已经在数据库中的charactersiconfolios

在控制台中我已经试过:

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

,但它不更新数据库。看来要删除的记录:

(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]] 

我也试过:

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

这产生:

(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 

这看起来很有希望,但数据库还没有更新。 (我试过重新启动服务器等)。

我在做什么错?

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 
+1

ActiveRecord的'has_one'的问题是,当你做'character.iconfolio'时,它会触发一个带有'LIMIT 1'子句的SQL查询。所以你的字符记录实际上可以有许多'iconfolios',但是你定义的关系('has_one')使得Rails只选择其中的一个。如果你做'Iconfolio.where(character_id:2)',你会看到几条记录(如果你没有删除你的例子中插入的数据),但是'Character.find(2).iconfolio'只会返回1条记录。总之,'has_one'不能确保你只有(最多)1条记录相关联,它只需抓住DB返回的第一条记录。 – MrYoshiji

+0

似乎你连续没有犯过什么错误。其中一个由@MrYoshiji描述,另一个 - 由MZaragoza在答案中。 –

+1

如果您想要更新所有现有记录以遵循新的数据库结构,则不应该使用Rails控制台来执行此操作(如果有一天您忘记在'db:migrate'之后执行此操作,或者如果稍后创建的迁移使用这个数据做额外的逻辑?你不能停止迁移,在Rails控制台中运行代码,然后重做迁移)。如果您的数据库结构发生更改并且当前数据需要更新,则必须在同一个迁移文件中执行此操作。 – MrYoshiji

回答

2

我喜欢做的是移动before_create的方法

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 

现在你可以随时调用它,当你需要

1

由于我评论你的问题,你不应该使用Rails的控制台来更新现有的数据来尊重新的数据库结构(我可以提供例子,如果你需要的话不会这样做)。

你应该改变德DB结构非常相同的迁移更新此数据:

# 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 

这可能是一个沉重的迁移运行,取决于Iconfolio的数量记录你在DB。您可以按照此处所示的方法进行操作,或者定义方法Iconfolio#update_icon_and_post_urls,但此方法仅用于Iconfolio创建或迁移过程中,因此可能会过度杀伤,长期污染Iconfolio模型,最重要的是可能会导致在重新运行迁移时遇到问题(请参阅我对该答案的评论)。

+0

有了这个例子,为什么不只是'Iconfolio.includes(:character).find_each {| iconfolio | iconfolio.set_attributes}'这消除了模型中的代码重复和迁移? – MZaragoza

+0

@MZaragoza这就是我在我的答案中所包含的内容,但这个逻辑中存在一个缺陷:如果明天我们为Iconfolio模型添加一个新的URL,并且相应地更新'set_attributes'方法,那么在运行迁移时它会调用该方法将尝试为尚不存在的列设置值。 Rails DB迁移应该包含所有逻辑并且不调用外部方法的众多例子之一。 (说实话,当我们创建一个迁移时,我们在迁移中定义了模型的类,以防将来有一天班级重新命名) – MrYoshiji

+0

我同意。我可以看到塞纳里奥正在发生。 – MZaragoza