mixed_gauge 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5cb22b7adaa5a1e4b9ef9b995f5bd5fe1793d366
4
- data.tar.gz: 334e18351f5e92c0a931aceff4fb8f294afee30b
3
+ metadata.gz: af67019109e77025f6dcc080900efb12622b9f7e
4
+ data.tar.gz: be1c25c01c401d06286e26e1075bbd29f5df5d6c
5
5
  SHA512:
6
- metadata.gz: a11c1dd6b9df580f0671d9cff99ce4d69d5c6aef2b26cfd378803bd8fa025875b988b549f9b866f230454e5b53cf09bdad246af0da61ddf337e22f1c04c4c1f4
7
- data.tar.gz: eb0683ff2d6d3df290edaa26a9e7391a169a7dc0e4b9f617d45f58f5d5d1e22a63df4655592d76c90bed54af2de14fc93ae4fb1f15ac4aa01fece1260f51fd46
6
+ metadata.gz: 70a434108a0fecee84f08f63d79a74a751c91396352dac7682be3031fbd80249a7dac288cdd2a137993411ff40b375697889d4c01a0409686bc4486421e88bf7
7
+ data.tar.gz: 0b415db64f93f88422fab59c290d504a65e546a2e4d49cb33bf75178f5f825a4e1fb4590ff199c25c6342407622412b700bfc01448c1d3f06400b8127d210028
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.1.2
2
+ - Enable to register before hook on `.put!`.
3
+ - Enable to define arbitrary methods to model class.
4
+ - Arbitrary hash function registeration.
5
+ - `.get!` to raise error when record not found.
6
+
1
7
  ## 0.1.1
2
8
  - Fix NoMethodError bug on MixedGauge::Model.
3
9
  - Improve tests, measure coverage.
data/README.md CHANGED
@@ -3,22 +3,6 @@
3
3
 
4
4
  An ActiveRecord extension for database sharding.
5
5
 
6
- ## Installation
7
-
8
- Add this line to your application's Gemfile:
9
-
10
- ```ruby
11
- gem 'mixed_gauge'
12
- ```
13
-
14
- And then execute:
15
-
16
- $ bundle
17
-
18
- Or install it yourself as:
19
-
20
- $ gem install mixed_gauge
21
-
22
6
  ## Usage
23
7
 
24
8
  Add additional database connection config to `database.yml`.
@@ -81,9 +65,88 @@ alice = User.get('alice@example.com')
81
65
  alice.age = 1
82
66
  alice.save!
83
67
 
84
- User.all_shards.flat_map {|m| m.where(name: 'alice') }.compact
68
+ User.all_shards.flat_map {|m| m.find_by(name: 'alice') }.compact
85
69
  ```
86
70
 
71
+ When you want find by non-distkey, not recomended though, you can define finder
72
+ methods to model class.
73
+
74
+ ```ruby
75
+ class User < ActiveRecord::Base
76
+ include MixedGauge::Model
77
+ use_cluster :user
78
+ def_distkey :email
79
+
80
+ parent_methods do
81
+ def find_from_all_by_name(name)
82
+ all_shards.map {|m| m.find_by(name: name) }.compact.first
83
+ end
84
+ end
85
+ end
86
+
87
+ alice = User.find_from_all_by_name('Alice')
88
+ alice.age = 0
89
+ alice.save!
90
+ ```
91
+
92
+ Sometimes you want to generates distkey value before validation. Since mixed_gauge
93
+ generates sub class of your models, AR's callback is not usesless for this usecase,
94
+ so mixed_gauge offers its own callback method.
95
+
96
+ ```ruby
97
+ class AccessToken < ActiveRecord::Base
98
+ include MixedGauge::Model
99
+ use_cluster :access_token
100
+ def_distkey :token
101
+
102
+ validates :token, presence: true
103
+
104
+ def self.generate_token
105
+ SecureRandom.uuid
106
+ end
107
+
108
+ before_put do |attributes|
109
+ unless attributes[:token] || attributes['token']
110
+ attributes[:token] = generate_token
111
+ end
112
+ end
113
+ end
114
+
115
+ access_token = AccessToken.put!
116
+ access_token.token #=> a generated token
117
+ ```
118
+
119
+ ## Advanced configuration
120
+ ### Hash fucntion
121
+ Register arbitrary hash function. Hash function must be a proc and
122
+ must return integer.
123
+
124
+ ```ruby
125
+ # gem install fnv
126
+ require "fnv"
127
+ Mixedgauge.configure do |config|
128
+ config.register_hash_function do |key|
129
+ FNV.new.fnv1a_64(key)
130
+ end
131
+ end
132
+ ```
133
+
134
+ ## Installation
135
+
136
+ Add this line to your application's Gemfile:
137
+
138
+ ```ruby
139
+ gem 'mixed_gauge'
140
+ ```
141
+
142
+ And then execute:
143
+
144
+ $ bundle
145
+
146
+ Or install it yourself as:
147
+
148
+ $ gem install mixed_gauge
149
+
87
150
  ## Development
88
151
 
89
152
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,14 +1,22 @@
1
1
  module MixedGauge
2
2
  class Config
3
+ DEFAULT_HASH_FUNCTION = -> (key) { Digest::MD5.hexdigest(key).to_i(16) }
4
+
5
+ attr_reader :hash_proc
6
+
3
7
  def initialize
4
8
  @cluster_configs = {}
9
+ @hash_proc = DEFAULT_HASH_FUNCTION
5
10
  end
6
11
 
12
+ # Define config for specific cluster.
7
13
  # @param [Symbol] cluster_name
8
14
  # @return [nil]
9
15
  # @example
10
- # config.define_cluster(:user) do |c|
11
- # c.define_slots(1..1024)
16
+ # config.define_cluster(:user) do |cluster|
17
+ # cluster.define_slots(1..1048576)
18
+ # cluster.register(1..524288, :production_user_001)
19
+ # cluster.register(524289..1048576, :production_user_002)
12
20
  # end
13
21
  def define_cluster(cluster_name, &block)
14
22
  cluster_config = ClusterConfig.new(cluster_name)
@@ -22,5 +30,21 @@ module MixedGauge
22
30
  def fetch_cluster_config(cluster_name)
23
31
  @cluster_configs.fetch(cluster_name)
24
32
  end
33
+
34
+ # Register arbitrary hash function. Hash function must be a proc and
35
+ # must return integer.
36
+ # @example
37
+ # # gem install fnv
38
+ # require "fnv"
39
+ # Mixedgauge.configure do |config|
40
+ # config.register_hash_function do |key|
41
+ # FNV.new.fnv1a_64(key)
42
+ # end
43
+ # end
44
+ def register_hash_function(&block)
45
+ raise ArgumentError if block.arity != 1
46
+ raise ArgumentError unless block.call('test value').is_a? Integer
47
+ @hash_proc = block
48
+ end
25
49
  end
26
50
  end
@@ -5,4 +5,8 @@ module MixedGauge
5
5
  # Raised when try to put new record without distkey attribute.
6
6
  class MissingDistkeyAttribute < Error
7
7
  end
8
+
9
+ # Inherit from AR::RecordNotFound to enable to handle as AR's one.
10
+ class RecordNotFound < ActiveRecord::RecordNotFound
11
+ end
8
12
  end
@@ -25,6 +25,7 @@ module MixedGauge
25
25
  end
26
26
 
27
27
  module ClassMethods
28
+ # The cluster config must be defined before `use_cluster`.
28
29
  # @param [Symbol] A cluster name which is set by MixedGauge.configure
29
30
  def use_cluster(name)
30
31
  config = MixedGauge.config.fetch_cluster_config(name)
@@ -33,20 +34,22 @@ module MixedGauge
33
34
  self.abstract_class = true
34
35
  end
35
36
 
37
+ # Distkey is a column. mixed_gauge hashes that value and determine which
38
+ # shard to store.
36
39
  # @param [Symbol] column
37
40
  def def_distkey(column)
38
41
  self.distkey = column.to_sym
39
42
  end
40
43
 
41
- # @param [Object] key
42
- # @return [ActiveRecord::Base] A auto-generated sub model of included model
43
- def get(key)
44
- shard_for(key.to_s).find_by(distkey => key)
45
- end
46
-
44
+ # Create new record with given attributes in proper shard for given key.
45
+ # When distkey value is empty, raises MixedGauge::MissingDistkeyAttribute
46
+ # error.
47
47
  # @param [Hash] attributes
48
48
  # @return [ActiveRecord::Base] A sub class instance of included model
49
+ # @raise [MixedGauge::MissingDistkeyAttribute]
49
50
  def put!(attributes)
51
+ @before_put_callback.call(attributes) if @before_put_callback
52
+
50
53
  if key = attributes[distkey] || attributes[distkey.to_s]
51
54
  shard_for(key).create!(attributes)
52
55
  else
@@ -54,17 +57,84 @@ module MixedGauge
54
57
  end
55
58
  end
56
59
 
57
- # @param [Object] key A value of distkey
60
+ # Returns nil when not found. Except that, is same as `.get!`.
61
+ # @param [String] key
62
+ # @return [ActiveRecord::Base, nil] A sub model instance of included model
63
+ def get(key)
64
+ raise 'key must be a String' unless key.is_a?(String)
65
+ shard_for(key.to_s).find_by(distkey => key)
66
+ end
67
+
68
+ # `.get!` raises MixedGauge::RecordNotFound which is child class of
69
+ # `ActiveRecord::RecordNotFound` so you can rescue that exception as same
70
+ # as AR's RecordNotFound.
71
+ # @param [String] key
72
+ # @return [ActiveRecord::Base] A sub model instance of included model
73
+ # @raise [MixedGauge::RecordNotFound]
74
+ def get!(key)
75
+ get(key) or raise MixedGauge::RecordNotFound
76
+ end
77
+
78
+ # Register hook to assign auto-generated distkey or something.
79
+ # Sometimes you want to generates distkey value before validation. Since
80
+ # mixed_gauge generates sub class of your models, AR's callback is not
81
+ # usesless for this usecase, so mixed_gauge offers its own callback method.
82
+ # @example
83
+ # class User
84
+ # include MixedGauge::Model
85
+ # use_cluster :user
86
+ # def_distkey :name
87
+ # before_put do |attributes|
88
+ # attributes[:name] = generate_name unless attributes[:name]
89
+ # end
90
+ # end
91
+ def before_put(&block)
92
+ @before_put_callback = block
93
+ end
94
+
95
+ # Returns a generated sub class of this model which is connected proper
96
+ # shard for given key.
97
+ # @param [String] key A value of distkey
58
98
  # @return [Class] A sub model for this distkey value
59
99
  def shard_for(key)
60
100
  connection_name = cluster_routing.route(key.to_s)
61
101
  sub_model_repository.fetch(connection_name)
62
102
  end
63
103
 
104
+ # Returns all generated sub class of this model. Useful to query to
105
+ # all shards.
64
106
  # @return [Array<Class>] An array of sub models
107
+ # @example
108
+ # User.all_shards.flat_map {|m| m.find_by(name: 'alice') }.compact
65
109
  def all_shards
66
110
  sub_model_repository.all
67
111
  end
112
+
113
+ # Define utility methods which uses all shards or specific shard.
114
+ # These methods can be called from included model class.
115
+ # @example
116
+ # class User
117
+ # include MixedGauge::Model
118
+ # use_cluster :user
119
+ # def_distkey :name
120
+ # parent_methods do
121
+ # def all_count
122
+ # all_shards.map {|m| m.count }.reduce(&:+)
123
+ # end
124
+ #
125
+ # def find_from_all_by(condition)
126
+ # all_shards.flat_map {|m m.find_by(condition) }.compact.first
127
+ # end
128
+ # end
129
+ # end
130
+ #
131
+ # User.put!(email: 'a@m.com', name: 'a')
132
+ # User.put!(email: 'b@m.com', name: 'b')
133
+ # User.all_count #=> 2
134
+ # User.find_from_all_by(name: 'b') #=> User b
135
+ def parent_methods(&block)
136
+ instance_eval(&block)
137
+ end
68
138
  end
69
139
  end
70
140
  end
@@ -17,7 +17,7 @@ module MixedGauge
17
17
  # @param [String] key
18
18
  # @return [Integer]
19
19
  def hash_f(key)
20
- Digest::MD5.hexdigest(key).to_i(16)
20
+ MixedGauge.config.hash_proc.call(key)
21
21
  end
22
22
  end
23
23
  end
@@ -1,3 +1,3 @@
1
1
  module MixedGauge
2
- VERSION = '0.1.1'
2
+ VERSION = '0.1.2'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixed_gauge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taiki Ono