From 00ceb758084ae9398411baeb6d531fc56924466c Mon Sep 17 00:00:00 2001 From: Justin Cypret Date: Tue, 29 Oct 2019 21:59:15 -0500 Subject: [PATCH 1/2] Add support for model-level config This adds a `hashid_config` class method to allowing for overriding the default hashid config. The supports all the existing configuration options. It was added particularly to address the issue of changing a table name without changing the hashids. Example usage: class MyModel < ApplicationRecord include Hashid::Rails hashid_config pepper: "old_table" end --- lib/hashid/rails.rb | 25 +++++++++-- lib/hashid/rails/configuration.rb | 6 ++- spec/hashid/rails_spec.rb | 70 ++++++++++++++++++++++++++++++- 3 files changed, 93 insertions(+), 8 deletions(-) diff --git a/lib/hashid/rails.rb b/lib/hashid/rails.rb index bceeb90..a77101a 100644 --- a/lib/hashid/rails.rb +++ b/lib/hashid/rails.rb @@ -36,6 +36,23 @@ def hashid alias to_param hashid module ClassMethods + def hashid_config(options = {}) + config = Hashid::Rails.configuration.dup + config.pepper = table_name + options.each do |attr, value| + config.public_send("#{attr}=", value) + end + @hashid_configuration = config + end + + def hashid_configuration + @hashid_configuration || hashid_config + end + + def reset_hashid_config + @hashid_configuration = nil + end + def relation super.tap { |r| r.extend ClassMethods } end @@ -71,7 +88,7 @@ def find(*ids) uniq_ids = ids.flatten.compact.uniq uniq_ids = uniq_ids.first unless expects_array || uniq_ids.size > 1 - if Hashid::Rails.configuration.override_find + if hashid_configuration.override_find super(decode_id(uniq_ids, fallback: true)) else super @@ -89,11 +106,11 @@ def find_by_hashid!(hashid) private def hashids - Hashids.new(*Hashid::Rails.configuration.for_table(table_name)) + Hashids.new(*hashid_configuration.to_args) end def hashid_encode(id) - if Hashid::Rails.configuration.sign_hashids + if hashid_configuration.sign_hashids hashids.encode(HASHID_TOKEN, id) else hashids.encode(id) @@ -104,7 +121,7 @@ def hashid_decode(id, fallback:) fallback_value = fallback ? id : nil decoded_hashid = hashids.decode(id.to_s) - if Hashid::Rails.configuration.sign_hashids + if hashid_configuration.sign_hashids valid_hashid?(decoded_hashid) ? decoded_hashid.last : fallback_value else decoded_hashid.first || fallback_value diff --git a/lib/hashid/rails/configuration.rb b/lib/hashid/rails/configuration.rb index 28b55e3..1cf226d 100644 --- a/lib/hashid/rails/configuration.rb +++ b/lib/hashid/rails/configuration.rb @@ -4,6 +4,7 @@ module Hashid module Rails class Configuration attr_accessor :salt, + :pepper, :min_hash_length, :alphabet, :override_find, @@ -11,6 +12,7 @@ class Configuration def initialize @salt = "" + @pepper = "" @min_hash_length = 6 @alphabet = "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ @@ -19,8 +21,8 @@ def initialize @sign_hashids = true end - def for_table(table_name) - ["#{table_name}#{salt}", min_hash_length, alphabet] + def to_args + ["#{pepper}#{salt}", min_hash_length, alphabet] end end end diff --git a/spec/hashid/rails_spec.rb b/spec/hashid/rails_spec.rb index c14aa86..f4321c5 100644 --- a/spec/hashid/rails_spec.rb +++ b/spec/hashid/rails_spec.rb @@ -3,7 +3,12 @@ require "spec_helper" describe Hashid::Rails do - before(:each) { Hashid::Rails.reset } + before(:each) do + Hashid::Rails.reset + FakeModel.reset_hashid_config + Post.reset_hashid_config + Comment.reset_hashid_config + end describe "#hashid" do it "returns model ID encoded as hashid" do @@ -284,13 +289,15 @@ Hashid::Rails.configure do |config| config.override_find = true end - result = FakeModel.find(model.hashid) + FakeModel.reset_hashid_config + result = FakeModel.find(model.hashid) expect(result).to eq(model) Hashid::Rails.configure do |config| config.override_find = false end + FakeModel.reset_hashid_config expect { FakeModel.find(model.hashid) } .to raise_error(ActiveRecord::RecordNotFound) @@ -398,6 +405,65 @@ expect(config.sign_hashids).to eq(false) end end + + it "inherits default configuration" do + config = FakeModel.hashid_configuration + + aggregate_failures "default config" do + expect(config.salt).to eq("") + expect(config.pepper).to eq(FakeModel.table_name) + expect(config.min_hash_length).to eq(6) + expect(config.alphabet).to eq( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" + ) + expect(config.override_find).to eq(true) + expect(config.sign_hashids).to eq(true) + end + end + + it "supports model-specific config" do + FakeModel.hashid_config( + salt: "shhh", + pepper: "achoo", + min_hash_length: 7, + alphabet: "XYZ", + override_find: false, + sign_hashids: false + ) + + # model-level has new config + config = FakeModel.hashid_configuration + aggregate_failures "model-level config" do + expect(config.salt).to eq("shhh") + expect(config.pepper).to eq("achoo") + expect(config.min_hash_length).to eq(7) + expect(config.alphabet).to eq("XYZ") + expect(config.override_find).to eq(false) + expect(config.sign_hashids).to eq(false) + end + + # default config does not change + config = Hashid::Rails.configuration + aggregate_failures "default config" do + expect(config.salt).to eq("") + expect(config.pepper).to eq("") + expect(config.min_hash_length).to eq(6) + expect(config.alphabet).to eq( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" + ) + expect(config.override_find).to eq(true) + expect(config.sign_hashids).to eq(true) + end + end + + it "supports different configs for each model" do + Post.hashid_config(pepper: "achoo") + Comment.hashid_config(pepper: "gazoontite") + + expect(FakeModel.hashid_configuration.pepper).to eq(FakeModel.table_name) + expect(Post.hashid_configuration.pepper).to eq("achoo") + expect(Comment.hashid_configuration.pepper).to eq("gazoontite") + end end describe ".reset" do From 4daecf1947817ded7e766aef4b093a50a453a9f1 Mon Sep 17 00:00:00 2001 From: Justin Cypret Date: Tue, 29 Oct 2019 22:46:37 -0500 Subject: [PATCH 2/2] Update readme for model-level config and bump version --- CHANGELOG.md | 3 +++ README.md | 21 +++++++++++++++++++-- lib/hashid/rails/version.rb | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4303b8..1848d62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 1.3.0 (2019-10-29) +- Add support for model-level config ([#67](https://github.com/jcypret/hashid-rails/pull/67)) + ## 1.2.2 (2018-07-29) ### Fixed - Handle exception raised when using a letter-only alphabet and attempting to diff --git a/README.md b/README.md index c14da9e..f9cc7df 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ model.hashid #=> "yLA6m0oM" ``` -Additionally, the `to_param` method is overriden to use hashid instead of id. +Additionally, the `to_param` method is overridden to use hashid instead of id. This means methods that take advantage of implicit ID will automatically work with hashids. @@ -94,8 +94,9 @@ default options. ```ruby Hashid::Rails.configure do |config| - # The salt to use for generating hashid. Prepended with table name. + # The salt to use for generating hashid. Prepended with pepper (table name). config.salt = "" + config.pepper = table_name # The minimum length of generated hashids config.min_hash_length = 6 @@ -113,6 +114,22 @@ Hashid::Rails.configure do |config| end ``` +### Model-Level Config + +You can also customize the hashid configuration at the model level. +`hashid_config` supports all the same options as the `Hashid::Rails.configure` +block and allows for each model to have a different config. This can be useful +for setting a custom salt/pepper. For instance, the pepper defaults to the table +name, so if you rename the table, you can keep the same hashids by setting the +pepper to the old table name. + +```ruby +class Model < ActiveRecord::Base + include Hashid::Rails + hashid_config pepper: "old_table_name" +end +``` + ## Upgrading from Pre-1.0 The 1.0 release of this gem introduced hashid signing to prevent diff --git a/lib/hashid/rails/version.rb b/lib/hashid/rails/version.rb index b95641d..32dd2ed 100644 --- a/lib/hashid/rails/version.rb +++ b/lib/hashid/rails/version.rb @@ -2,6 +2,6 @@ module Hashid module Rails - VERSION = "1.2.2" + VERSION = "1.3.0" end end