This simple gem allows specifying uniqueness groups for an attribute, giving you something to expose to the outside world.
When do you need this?
- If you're sharding your database.
- If you have multiple tables that you want to expose a unique ID for.
Installation
Add this line to your application's Gemfile:
gem 'unique_by'
And then execute:
$ bundle
Or install it yourself as:
$ gem install unique_by
Usage
Sharding example
You first need to specify a unique group in your model:
# == Schema Info
#
# Table name: medical_bills
#
# id :integer(11) not null, primary key
# client_id :integer(11)
#
class MedicalBill < ActiveRecord::Base
unique_by client_id: 50 # total of 50 clients
end
then, you can use these basic methods:
bill1 = MedicalBill.find(123) # from a DB shard for client_id = 1
bill2 = MedicalBill.find(123) # from a DB shard for client_id = 2
bill1.unique_id
=> 7873
bill2.unique_id
=> 7874
You can use the singleton methods:
MedicalBill.unique_id_from(123, client_id: 1) # gives the unique_id
=> 7873
MedicalBill.id_from(7873) # gives the id
=> 123
MedicalBill.id_group_from(7874) # gives the client_id
=> { client_id: 2 }
You can specify multiple unique group attributes:
class MedicalBill < ActiveRecord::Base
unique_by client_id: 50, client_part: 5 # total of 50 clients and 5 parts
end
It is recommended to create finder methods that will find records according to their id group, like so:
class MedicallBill < ActiveRecord::Base
unique_by client_id: 50, client_part: 5 # total of 50 clients and 5 parts
def self.find_by_unique_id(unique_id)
withing_client_connection(id_group_from(unique_id)[:client_id]) do
find(id_from(unique_id))
end
end
end
Multiple tables example
You can supply a block to give a custom mechanism for determining the group:
class MedicalBill < ActiveRecord::Base
unique_by(type_index: 2) { { type_index: 0 } }
end
class UtilityBill < ActiveRecord::Base
unique_by(type_index: 2) { { type_index: 1 } }
end
Groups can also be served as a singleton method:
class MedicalBill < ActiveRecord::Base
unique_by type_index: 2
def self.type_index
0
end
end
class UtilityBill < ActiveRecord::Base
unique_by type_index: 2
def self.type_index
1
end
end
In either case, if all the groups are specified as singleton methods or served
within the block WITHOUT using the instance (self
), you can use the singleton
methods without explicitly specifying the groups for this class:
MedicalBill.unique_id_from(123)
=> 247
UtilityBill.unique_id_from(123)
=> 246
It is recommended to create finder methods that will find records according to their id group, like so:
module Bill
module_function
def find_by_unique_id(unique_id)
case MedicalBill.id_group_from(unique_id)[:type_index]
when MedicalBill.type_index then MedicalBill.find(MedicalBill.id_from(unique_id))
when UtilityBill.type_index then UtilityBill.find(UtilityBill.id_from(unique_id))
end
end
end
You can supply both group attributes and a block, and the block can also return more than one field:
class MedicalBill < ActiveRecord::Base
unique_by(client_id: 50, client_part: 5, xy: 10, halfz: 20) do
{ xy: self.x * self.y, halfz: self.z / 2 }
end
end
Not ActiveRecord
The generator module is already included in ActiveRecord::Base
, but if
you want the above methods in another class you can extend it:
class MyClass
extend UniqueBy::Generator
unique_by ..., primary_key: :id
#
# OR
#
def self.primary_key
:id # or 'id'
end
end
See also
After adding the unique groups to the id, the unique_id might turn out pretty large. You could use rebase_attr to fix that:
class MedicalBill < ActiveRecord::Base
unique_by client_id: 32**2 # two base32 letters
rebase_attr :unique_id, to: 32, readable: true # digits and leters, without '0', 'o', '1' and 'l'
end
bill = MedicalBill.find(3528918) # from a DB shard for client_id = 78
bill.unique_id
=> "ywr3b2e"
bill.unique_id_without_rebase
=> 1806806094
Contributing
- Fork it ( https://github.com/odedniv/unique_by/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request