mixed_gauge 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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