在Rails中使用Postgres UUID时,在Join记录上创建,保存,更新和销毁

时间:2022-04-16 21:49:00

I'm creating uids using

我正在创建uids

create_table :users, { id: false } do |t|
    t.uuid :uid, default: 'uuid_generate_v4()'
    ... other columns

and setting self.primary_key = :uid in the models.

并在模型中设置self.primary_key =:uid。

In general this works fine with ActiveRecord and I write has_many and belongs_to associations fine. However, when crossing a join table (i.e. has_many ... through:, I need to write custom SQL to get records.

一般情况下,这适用于ActiveRecord,我写has_many和belongs_to协会罚款。但是,当跨越连接表(即has_many ... through:时,我需要编写自定义SQL来获取记录。

I've figured out that I can in general do this by writing custom SQL, i.e. SELECT * FROM main_table JOIN join_table ON main_table.uid = cast(join_table.uid AS uuid) WHERE condition=true)

我已经发现我通常可以通过编写自定义SQL来做到这一点,即SELECT * FROM main_table JOIN join_table ON main_table.uid = cast(join_table.uid AS uuid)WHERE condition = true)

I've just recently realized that ActiveRecord's create, destroy, save and update dont work on the join model.

我刚刚意识到ActiveRecord的创建,销毁,保存和更新不适用于连接模型。

I have patched the four methods so they work, but it's too complex a sequence for my taste and probably unoptimal. Here are my patches:

我修补了四种方法,所以它们可以工作,但这对我来说太复杂了,可能还不够理想。这是我的补丁:

def save(*args)
    # save sometimes works if it is called twice,
    # and sometimes works the first time but says theres an error
    super(*args) unless (super(*args) rescue true)
end

Sometimes, save issues a ROLLBACK the first time with no explanation. Then it works the second time. In other situations (I'm not sure why, possibly when updating), the first time it goes through successfully but if called a second time raises a TypeError. See here for another question about this error which doesn't have any answers for how to save a join when using uid instead of id. Here are my other (working) patches.

有时,保存第一次发出ROLLBACK而没有任何解释。然后它第二次工作。在其他情况下(我不确定为什么,可能在更新时),第一次成功通过,但如果第二次调用则引发TypeError。请参阅此处以获取有关此错误的另一个问题,该问题在使用uid而不是id时如何保存连接没有任何答案。这是我的其他(工作)补丁。

def create(*args)
    attrs = args[0]
    raise( ArgumentError, "invalid args to bucket list create" ) unless attrs.is_a?(Hash)
    bucket_list_photo = self.class.new(
        attrs
    )
    bucket_list_photo.save
    bucket_list_photo = BucketListPhoto.find_by(
        bucket_list_uid: bucket_list_photo.bucket_list_uid,
        photo_uid: bucket_list_photo.photo_uid
    )
    return bucket_list_photo
end

def update(*args)
    # similar bug to save
    attrs = args[0]
    raise( ArgumentError, "invalid args to bucket list update" ) unless attrs.is_a?(Hash)
    bucket_list_uid = self.bucket_list_uid
    photo_uid = self.photo_uid
    due_at = self.due_at
    self.destroy
    bucket_list_photo = self.class.new(
        {
            bucket_list_uid: bucket_list_uid,
            photo_uid: photo_uid,
            due_at: due_at
        }.merge(attrs)
    )
    bucket_list_photo.save
    bucket_list_photo = self.class.find_by(
        photo_uid: photo_uid,
        bucket_list_uid: bucket_list_uid
    )
    return bucket_list_photo # phew
end

def destroy
    # patching to fix an error on #destroy, #destroy_all etc.
    # the problem was apparently caused by custom primary keys (uids)
    # see https://*.com/a/26029997/2981429
    # however a custom fix is implemented here
    deleted_uids = ActiveRecord::Base.connection.execute(
        "DELETE FROM bucket_list_photos WHERE uid='#{uid}' RETURNING uid"
    ).to_a.map { |record| record['uid'] }
    raise "BucketListPhoto not deleted" unless (
        (deleted_uids.length == 1) && (deleted_uids.first == uid)
    )
    ActiveRecord::Base.connection.query_cache.clear
    # since, the cache isnt updated when using ActiveRecord::Base.connection.execute,
    # reset the cache to ensure accurate values, i.e. counts and associations. 
end

I even ensured that self.primary_key = :uid in all my models.

我甚至在所有模特中确保了self.primary_key =:uid。

I also tried replacing uid with id everywhere and verified that all the specs were passing (though I left in the patch). However, it still failed when I removed the patch (i.e. renaming the uid columns to id did not fix it).

我也尝试用id替换uid,并验证所有规格都已经过去了(虽然我留在了补丁中)。但是,当我删除补丁时,它仍然失败(即将uid列重命名为id并未修复它)。

EDIT

In response to some comments I've tried the activeuuid gem (where i got stuck on an error) and decided to totally switch over to ids. This is basically for simplicity's sake since I have pressure to launch this app ASAP.

为了回应一些评论,我尝试了activeuuid gem(我遇到了错误),并决定完全切换到ID。这基本上是为了简单起见,因为我有压力尽快启动这个应用程序。

Still, even with this fix I am required to patch save, create, and update. Actually the delete patch no longer works and I had to remove it (relying on the original). I would definitely like to avoid having to make these patches and I am keeping the bounty open for this reason.

尽管如此,即使使用此修复程序,我仍需要修补保存,创建和更新。实际上删除补丁不再有效,我不得不将其删除(依赖原始版本)。我肯定希望避免制作这些补丁,因此我保持赏金。

2 个解决方案

#1


6  

There are pros and cons to retaining both id and uuid. For JSON APIs that expose uuid, using Concerns would be a Rails-ish implementation.

保留id和uuid都有利有弊。对于公开uuid的JSON API,使用Concerns将是一个Rails-ish实现。

app/models/user.rb

class User < ActiveRecord::Base

    include UserConcerns::Uuidable

end

app/models/concerns/user_concerns/uuidable.rb

module UserConcerns::Uuidable
    extend ActiveSupport::Concern

    included do
        before_save :ensure_uuid!
    end


    def ensure_uuid!
        self.uuid = generate_uuid if uuid.blank?
    end

    def generate_uuid
        # your uuid using uuid_generate_v4() here
    end

    def to_param
        uuid
    end
end

Above implementation leaves out the uuid generation but I think above answer has a link to that.

上面的实现遗漏了uuid代,但我认为上面的答案有一个链接。

#2


3  

I have provided 1 solution to generate UUID, I knew you switched back to id now. find link for UUID

我提供了1个生成UUID的解决方案,我知道你现在切换回id。找到UUID的链接

Yes I agree we can not perform CRUD on joins table why don't you use active records relations to perform CRUD operations.

是的我同意我们不能在连接表上执行CRUD为什么不使用活动记录关系来执行CRUD操作。

The important thing to understand is the convention by which Rails implements relationships using ActiveRecord. A book has many characters, and each character belongs to a book, so:

要理解的重要事项是Rails使用ActiveRecord实现关系的约定。一本书有很多字,每个字都属于一本书,所以:

class Book < ActiveRecordBase
  has_many :characters
end

class Character < ActiveRecordBase
  belongs_to :book
end

Rails now assumes that the characters table will have a foreign key called book_id, which relates to the books table. To create a character belonging to a book:

Rails现在假设字符表将有一个名为book_id的外键,它与books表有关。要创建属于书籍的角色:

@book = Book.new(:name=>"Book name")
@character = @book.characters.build(:name=>"Character name")

Now when @book is saved (assuming both @book and @character are valid), a row will be created in both the books and the characters tables, with the character row linked through book_id.

现在保存@book时(假设@book和@character都有效),将在书籍和字符表中创建一行,并通过book_id链接字符行。

To show that a character also belongs to a user, you could add that relationship to the Character model:

要显示角色也属于用户,您可以将该关系添加到角色模型:

class Character < ActiveRecordBase
  belongs_to :book
  belongs_to :user
end

Thus Rails now expects characters to also have foreign key called user_id, which points to a users table (which also needs a User model). To specify the user when creating the character:

因此,Rails现在期望字符也具有名为user_id的外键,其指向用户表(其还需要用户模型)。在创建角色时指定用户:

@book = Book.new(:name=>"Book name")
@character = @book.characters.build(:name=>"Character name",:user=>current_user)

You can also assign the foreign key by calling the corresponding method on the object:

您还可以通过调用对象上的相应方法来分配外键:

@character.user = current_user

This all works because it follows the Rails conventions for naming models and tables.

这一切都有效,因为它遵循Rails约定命名模型和表。

#1


6  

There are pros and cons to retaining both id and uuid. For JSON APIs that expose uuid, using Concerns would be a Rails-ish implementation.

保留id和uuid都有利有弊。对于公开uuid的JSON API,使用Concerns将是一个Rails-ish实现。

app/models/user.rb

class User < ActiveRecord::Base

    include UserConcerns::Uuidable

end

app/models/concerns/user_concerns/uuidable.rb

module UserConcerns::Uuidable
    extend ActiveSupport::Concern

    included do
        before_save :ensure_uuid!
    end


    def ensure_uuid!
        self.uuid = generate_uuid if uuid.blank?
    end

    def generate_uuid
        # your uuid using uuid_generate_v4() here
    end

    def to_param
        uuid
    end
end

Above implementation leaves out the uuid generation but I think above answer has a link to that.

上面的实现遗漏了uuid代,但我认为上面的答案有一个链接。

#2


3  

I have provided 1 solution to generate UUID, I knew you switched back to id now. find link for UUID

我提供了1个生成UUID的解决方案,我知道你现在切换回id。找到UUID的链接

Yes I agree we can not perform CRUD on joins table why don't you use active records relations to perform CRUD operations.

是的我同意我们不能在连接表上执行CRUD为什么不使用活动记录关系来执行CRUD操作。

The important thing to understand is the convention by which Rails implements relationships using ActiveRecord. A book has many characters, and each character belongs to a book, so:

要理解的重要事项是Rails使用ActiveRecord实现关系的约定。一本书有很多字,每个字都属于一本书,所以:

class Book < ActiveRecordBase
  has_many :characters
end

class Character < ActiveRecordBase
  belongs_to :book
end

Rails now assumes that the characters table will have a foreign key called book_id, which relates to the books table. To create a character belonging to a book:

Rails现在假设字符表将有一个名为book_id的外键,它与books表有关。要创建属于书籍的角色:

@book = Book.new(:name=>"Book name")
@character = @book.characters.build(:name=>"Character name")

Now when @book is saved (assuming both @book and @character are valid), a row will be created in both the books and the characters tables, with the character row linked through book_id.

现在保存@book时(假设@book和@character都有效),将在书籍和字符表中创建一行,并通过book_id链接字符行。

To show that a character also belongs to a user, you could add that relationship to the Character model:

要显示角色也属于用户,您可以将该关系添加到角色模型:

class Character < ActiveRecordBase
  belongs_to :book
  belongs_to :user
end

Thus Rails now expects characters to also have foreign key called user_id, which points to a users table (which also needs a User model). To specify the user when creating the character:

因此,Rails现在期望字符也具有名为user_id的外键,其指向用户表(其还需要用户模型)。在创建角色时指定用户:

@book = Book.new(:name=>"Book name")
@character = @book.characters.build(:name=>"Character name",:user=>current_user)

You can also assign the foreign key by calling the corresponding method on the object:

您还可以通过调用对象上的相应方法来分配外键:

@character.user = current_user

This all works because it follows the Rails conventions for naming models and tables.

这一切都有效,因为它遵循Rails约定命名模型和表。