项目作者: mcelicalderon

项目描述 :
Helper methods for UUID associations in Active Record
高级语言: Ruby
项目地址: git://github.com/mcelicalderon/uuid_associations-active_record.git
创建时间: 2018-10-16T20:18:29Z
项目社区:https://github.com/mcelicalderon/uuid_associations-active_record

开源协议:MIT License

下载


UUID Associations ActiveRecord

CircleCI
Gem Version

Table of Contents

Installation

Add this line to your application’s Gemfile:

  1. gem 'uuid_associations-active_record'

And then execute:

  1. $ bundle

Or install it yourself as:

  1. $ gem install uuid_associations-active_record

Rationale

This gem is the result of some research I did to find out if using UUIDs as DB primary keys is a good thing
(Rails added support for this!). On the process
I ran into this article (among many others).
If you read the article you’ll see how UUIDs have benefits over using sequential Int values as primary keys, but also how
you should be aware of what does that imply in terms of performance or even amount of storage used.

One of the author’s conclusions is that you should use Int or Bigint columns as your primary keys, but also add a column
with a UUID that you can use to reference your records when exposing them to the outside world. If you finish reading the article
you’ll find out that not even this is the perfect solution, but at least it’s a huge improvement to exposing your sequential
primary keys. This gem helps you implement this approach.

Usage

Adding the gem to your Gemfile is all you need for the gem to start working if you are using Rails. If you are not, you
you can always install the gem and require it manually (probably right after you require active_record). Take a look at
specs if you want to use the gem with plain ActiveRecord as that is how they are setup.

What this gem does is add some useful methods to your ActiveRecord models by calling the good old association methods like
has_many, belongs_to, has_and_belongs_to_many. As you may already know, calling these methods creates multiple
methods on the calling model for you. I’ll explain the ones we are interested in in the next section.

This gem also adds a helpful mechanism to handle nested attributes. This will also be explained in the next section.

That’s it! That is what the gem does. This will allow you to pass UUIDs to your update or create operations instead
of the actual IDs. Be aware that this will of course require an additional (but simple) DB query. That is the cost of
hiding the actual IDs (I’d say it’s not too costly).

Also be aware that the initial version of this gem is not very configurable,
so the only thing it checks before creating the methods is that the right side association
on the caller model has a column named uuid, doesn’t take the column type into account.
I have tested with string columns in SQlite3 and UUID columns in Postgres.

Association Methods

Lets explore the next example:

  1. # app/models/user.rb
  2. class User < ActiveRecord::Base
  3. has_and_belongs_to_many :posts
  4. # generates
  5. #
  6. # def post_ids=(ids)
  7. # # Adds or deletes the association with posts based on the array of IDs received
  8. # end
  9. #
  10. # def post_ids
  11. # # returns an array with all the post IDs
  12. # end
  13. has_many :comments
  14. # generates
  15. #
  16. # def comment_ids=(ids)
  17. # # Adds or deletes the association with comments based on the array of IDs received
  18. # end
  19. #
  20. # def comment_ids
  21. # # returns an array with all the comment IDs
  22. # end
  23. end
  24. # app/models/post.rb
  25. class Post < ActiveRecord::Base
  26. has_and_belongs_to_many :users
  27. # generates
  28. #
  29. # def user_ids=(ids)
  30. # # Adds or deletes the association with users based on the array of IDs received
  31. # end
  32. #
  33. # def user_ids
  34. # # returns an array with all the user IDs
  35. # end
  36. end
  37. # app/models/comment.rb
  38. class Comment < ActiveRecord::Base
  39. belongs_to :user
  40. # generates
  41. #
  42. # def user_id=(id)
  43. # associates the comment with the user
  44. # end
  45. # def user_id
  46. # returns the ID of the associated user
  47. # end
  48. end

This gem will add two new methods for each call to one of the association methods.

Generated Methods

Calling any of these methods will also add the following methods to your model:

  1. # app/models/user.rb
  2. class User < ActiveRecord::Base
  3. has_and_belongs_to_many :posts
  4. # generates
  5. #
  6. # def post_uuids=(uuids)
  7. # self.post_ids = Post.where(uuid: uuids).pluck(:id)
  8. # end
  9. #
  10. # def post_uuids
  11. # posts.pluck(:uuid)
  12. # end
  13. has_many :comments
  14. # generates
  15. #
  16. # def comment_uuids=(uuids)
  17. # self.comment_ids = Comment.where(uuid: uuids).pluck(:id)
  18. # end
  19. #
  20. # def comment_uuids
  21. # comments.pluck(:uuid)
  22. # end
  23. end
  24. # app/models/post.rb
  25. class Post < ActiveRecord::Base
  26. has_and_belongs_to_many :users
  27. # generates
  28. #
  29. # def user_uuids=(uuids)
  30. # self.user_ids = User.where(uuid: uuids).pluck(:id)
  31. # end
  32. #
  33. # def user_uuids
  34. # users.pluck(:uuid)
  35. # end
  36. end
  37. # app/models/comment.rb
  38. class Comment < ActiveRecord::Base
  39. belongs_to :user
  40. # generates
  41. #
  42. # def user_uuid=(uuid)
  43. # self.user_id = User.find_by!(uuid: uuid)
  44. # end
  45. # def user_id
  46. # user.uuid
  47. # end
  48. end

Nested Attributes

Nested attributes don’t generate additional methods, this gem just modifies one so you can update nested record using
the record’s UUID instead of the actual ID. Let’s explore the next example:

  1. class Post < ActiveRecord::Base
  2. has_many :comments
  3. accepts_nested_attributes_for :comments, allow_destroy: true
  4. # generates
  5. #
  6. # def comments_attributes=(attributes)
  7. # # allows to create or update comments using this method on Post
  8. # end
  9. end

ActiveRecord allows for attributes to be an array of hashes or a hash of hashes. Here are some examples of the payload
you can pass to comments_attributes by using this gem:

  1. # This is supported without this gem
  2. [
  3. { id: 1, comment_body: 'updating comment with ID 1' },
  4. { id: 2, _destroy: true },
  5. { comment_body: 'this will create a new comment' }
  6. ]
  7. # With the gem, you can use UUIDs instead
  8. [
  9. { uuid: 'some-uuid', comment_body: 'updating comment with UUID: some-uuid' }.
  10. { uuid: 'other-uuid', _destroy: true },
  11. { comment_body: 'this will create a new comment' }
  12. ]

Here are some things to take into account:

  1. If the nested model (Comment in the example) does not have a column named uuid, the gem will take no action, will
    just preserve the original behavior.
  2. If the hash has both the :id and :uuid keys, the record will be fetched by id, and uuid will be passed as an attribute.
  3. When the hash has a :uuid key and no record is found for that key, an ActiveRecord::RecordNotFound error will be raised.
    If you want the behavior to be that a new record is created when not found by UUID, you can set the option create_missing_uuids: true
    on the accepts_nested_attributes_for call.

Future Work

  1. Not commonly used by me, but testing and adding these methods to a has_one relationship.
  2. Raise not found error if the array of UUIDs is bigger that the array of IDs fetched with the where statement (ActiveRecord’s behavior).

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To run the specs with all the supported versions of ActiveRecord you can use:

  1. $ bundle exec appraisal install
  2. $ bundle exec appraisal rspec

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/mcelicalderon/uuid_associations-active_record.

License

The gem is available as open source under the terms of the MIT License.