acts_as_shardable 0.7.0 → 0.8.0

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
- SHA1:
3
- metadata.gz: 11ce4ec7bf911d8b8eb80a615a3dc81a229f93b1
4
- data.tar.gz: 1cd6cc539c4bb61e219e6d7e402b8accc75306ac
2
+ SHA256:
3
+ metadata.gz: f0eac1d75f9a6c51a14d30662973d19a2eb191c2b02aae5cfbe2fd0564dc45d2
4
+ data.tar.gz: 501940171fa843435ce04ba2240f305fea243d738d1513b737e69d3292536035
5
5
  SHA512:
6
- metadata.gz: dea62a56bb346fe1616ca09af42773fec5fdf871b74371dcedfe7ed974d7e29f94a20981fa97bfd5c0778fe18e58d62a547a59c9a21ef0be11e335dab7692cd0
7
- data.tar.gz: 7b1798568e693b7dcf7d05e723df46b1b641a3892570418a531bf18b488b5c2eeebecb9044d6e9726be5b04109eb4eb680b0c0d0b9f788d07654be24c46aad34
6
+ metadata.gz: 3c5fc8e35e934d3fbd2aefcbdc24c8715bf9e13b25c98500ccd1eb7ec7b1a4aa62743b0381f67c7e2f10585dabd91de9b58f537a3ea2c0b70c1e7faa5e083c51
7
+ data.tar.gz: bd35953eb6fff25b02757df5948e6995aa2374b72062293708fd418d79ef44e9ff91295519e912f67b6eff010106b29927f3ed03b06940136efa26c250c5d175
data/README.md CHANGED
@@ -18,17 +18,25 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- Example
21
+ Examples
22
22
 
23
- In your model:
23
+ ### Built-in sharding method(mod)
24
24
 
25
25
  ```ruby
26
26
  class Mod2Model < ActiveRecord::Base
27
- acts_as_shardable by: :hash_id, mod: 2
27
+ acts_as_shardable by: :hash_id, using: :mod, args: { mod: 2 }
28
28
  end
29
29
  ```
30
30
 
31
- and migrations:
31
+ ### Customized sharding method
32
+
33
+ ```ruby
34
+ class Mod2Model < ActiveRecord::Base
35
+ acts_as_shardable by: :hash_id, using: ->(hash_id) { hash_id % 2 }
36
+ end
37
+ ```
38
+
39
+ ### Migrations
32
40
 
33
41
  ```ruby
34
42
  class CreateMod2Models < ActiveRecord::Migration
@@ -1,5 +1,7 @@
1
1
  require 'active_record'
2
2
  require "acts_as_shardable/version"
3
+ require "acts_as_shardable/attribute_methods"
4
+ require "acts_as_shardable/shard"
3
5
 
4
6
  module ActsAsShardable
5
7
 
@@ -10,207 +12,30 @@ module ActsAsShardable
10
12
  Mutex.new
11
13
  end
12
14
 
13
- def acts_as_shardable(by:, mod:)
14
- class_attribute :shard_method, :shard_mod, :module_name, :base_table_name
15
+ def acts_as_shardable(by:, using:, args: {})
16
+ class_attribute :shard_method, :shard_method_using, :shard_args, :module_name, :base_table_name
17
+
18
+ case using
19
+ when Symbol
20
+ # built-in functions
21
+ when Proc
22
+ # customized functions
23
+ else
24
+ raise ArgumentError, "unknown sharding function, `using` must be a symbol or proc"
25
+ end
15
26
 
16
27
  mutex.synchronize do
17
28
  self.shard_method = by
18
- self.shard_mod = mod
29
+ self.shard_method_using = using
30
+ self.shard_args = args
19
31
  self.module_name = self.name.deconstantize.safe_constantize || Object
20
32
  self.base_table_name = self.name.demodulize.pluralize.underscore
21
33
  self.table_name = "#{self.base_table_name}_0000"
22
34
  self.validates self.shard_method.to_sym, presence: true
23
-
24
- # Updates the associated record with values matching those of the instance attributes.
25
- # Returns the number of affected rows.
26
- define_method :_update_record do |attribute_names = self.attribute_names|
27
- attribute_names = keys_for_partial_write if self.class.base_class.partial_writes
28
-
29
- was, is = changes[self.class.base_class.shard_method]
30
-
31
- if was
32
- # shard_column changed
33
- table_was = self.class.base_class.sharding(was).table_name
34
- table_is = self.class.base_class.sharding(is).table_name
35
- raise WrongShardingError, "Please move from #{table_was} to #{table_is} manually."
36
- else
37
- # shard_column not changing
38
- if locking_enabled?
39
- lock_col = self.class.base_class.locking_column
40
- previous_lock_value = read_attribute(lock_col)
41
- self[lock_col] = previous_lock_value + 1
42
-
43
- attribute_names += [lock_col].compact
44
- attribute_names.uniq!
45
-
46
- begin
47
- relation = shard.unscoped
48
-
49
- affected_rows = relation.where(
50
- self.class.primary_key => id,
51
- lock_col => previous_lock_value,
52
- ).update_all(
53
- Hash[attributes_for_update(attribute_names).map do |name|
54
- [name, _read_attribute(name)]
55
- end]
56
- )
57
-
58
- unless affected_rows == 1
59
- raise ActiveRecord::StaleObjectError.new(self, "update")
60
- end
61
-
62
- affected_rows
63
-
64
- # If something went wrong, revert the version.
65
- rescue Exception
66
- send(lock_col + '=', previous_lock_value)
67
- raise
68
- end
69
- else
70
- if self.respond_to?(:attributes_with_values_for_update, true)
71
- attributes_values = attributes_with_values_for_update(attribute_names)
72
- else
73
- attribute_names = attributes_for_update(attribute_names)
74
- attributes_values = {}
75
- arel_table = shard.arel_table
76
-
77
- attribute_names.each do |name|
78
- attributes_values[arel_table[name]] = typecasted_attribute_value(name)
79
- end
80
- end
81
-
82
- if attributes_values.empty?
83
- 0
84
- else
85
- if ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR >= 1
86
- shard.unscoped._update_record(attributes_values, self.class.base_class.primary_key => id_in_database)
87
- else
88
- shard.unscoped._update_record(attributes_values, id, id_was)
89
- end
90
- end
91
- end
92
- end
93
- end
94
-
95
- private :_update_record
96
-
97
-
98
- define_method :touch do |*names|
99
- raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
100
-
101
- attributes = timestamp_attributes_for_update_in_model
102
- attributes.concat(names)
103
-
104
- unless attributes.empty?
105
- current_time = current_time_from_proper_timezone
106
- changes = {}
107
-
108
- attributes.each do |column|
109
- column = column.to_s
110
- changes[column] = write_attribute(column, current_time)
111
- end
112
-
113
- if locking_enabled?
114
- if self.respond_to?(:increment_lock)
115
- changes[self.class.base_class.locking_column] = increment_lock
116
- else
117
- lock_col = self.class.base_class.locking_column
118
- previous_lock_value = read_attribute(lock_col)
119
- self[lock_col] = previous_lock_value + 1
120
- changes[lock_col] = self[lock_col]
121
- end
122
- end
123
-
124
- clear_attribute_changes(changes.keys)
125
- primary_key = self.class.primary_key
126
- shard.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
127
- else
128
- true
129
- end
130
- end
131
-
132
- # Creates a record with values matching those of the instance attributes
133
- # and returns its id.
134
- define_method :_create_record do |attribute_names = self.attribute_names|
135
- _run_create_callbacks {
136
- attribute_names = keys_for_partial_write if self.class.base_class.partial_writes
137
- attribute_names |= [self.class.base_class.locking_column] if locking_enabled?
138
-
139
- if self.respond_to?(:attributes_with_values_for_create, true)
140
- attributes_values = attributes_with_values_for_create(attribute_names)
141
- else
142
- attributes_values = arel_attributes_with_values_for_create(attribute_names)
143
- end
144
-
145
- if shard.respond_to?(:_insert_record)
146
- new_id = shard.unscoped._insert_record attributes_values
147
- else
148
- new_id = shard.unscoped.insert attributes_values
149
- end
150
-
151
- self.id ||= new_id if self.class.base_class.primary_key
152
-
153
- @new_record = false
154
- id
155
- }
156
- end
157
-
158
- private :_create_record
159
-
160
- define_method :shard do
161
- shard_value = self.send(self.class.base_class.shard_method)
162
- self.class.base_class.sharding(shard_value)
163
- end
164
-
165
- private :shard
166
-
167
- self.class.send :define_method, :sharding do |column|
168
- i = column.to_i % shard_mod
169
- klass = "#{base_class.name.demodulize}_%04d" % i
170
- @@sharding_class ||= {}
171
- @@sharding_class[klass] ||= mutex.synchronize do
172
- if module_name.const_defined?(klass, false)
173
- module_name.const_get(klass, false)
174
- else
175
- Class.new(base_class) do
176
- self.table_name = ("#{base_class.base_table_name}_%04d" % i)
177
-
178
- if base_class.respond_to?(:protobuf_message)
179
- self.protobuf_message base_class.protobuf_message
180
-
181
- # Create a .to_proto method on XXX::ActiveRecord_Relation
182
- self.const_get('ActiveRecord_Relation', false).class_exec do
183
- def to_proto(*args)
184
- msg_class = base_class.name.demodulize.pluralize
185
- module_name = base_class.name.deconstantize.constantize
186
- module_name::Messages.const_get(msg_class, false).new(msg_class.underscore => map { |r| r.to_proto(*args) })
187
- end
188
- end
189
- end
190
-
191
- end.tap { |k| module_name.const_set(klass, k) }
192
- end
193
- end
194
- end
195
-
196
- define_method :reload do |options = nil|
197
- clear_aggregation_cache
198
- clear_association_cache
199
- shard.connection.clear_query_cache
200
-
201
- fresh_object =
202
- if options && options[:lock]
203
- shard.unscoped { shard.lock(options[:lock]).find(id) }
204
- else
205
- shard.unscoped { shard.find(id) }
206
- end
207
-
208
- @attributes = fresh_object.instance_variable_get('@attributes')
209
- @new_record = false
210
- fresh_object
211
- end
212
35
  end
213
36
 
37
+ include AttributeMethods
38
+ include Shard
214
39
  end
215
40
 
216
41
  end
@@ -0,0 +1,14 @@
1
+ module ActsAsShardable
2
+ module AttributeMethods
3
+ private
4
+
5
+ # @overload
6
+ def _assign_attribute(k, v)
7
+ if persisted? && k.to_s == self.class.base_class.shard_method.to_s
8
+ raise WrongShardingError, "The sharding key #{k} can't be change"
9
+ end
10
+
11
+ super
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,72 @@
1
+ module ActsAsShardable
2
+ module Shard
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ alias :_class :class
7
+
8
+ def shard
9
+ if defined? @attributes
10
+ shard_value = self.attributes[_class.base_class.shard_method.to_s]
11
+ if shard_value
12
+ _class.base_class.sharding(shard_value)
13
+ else
14
+ _class
15
+ end
16
+ else
17
+ _class
18
+ end
19
+ end
20
+
21
+ alias :class :shard
22
+ end
23
+
24
+ class_methods do
25
+
26
+ def sharding(column)
27
+ i = case self.shard_method_using
28
+ when Proc
29
+ self.shard_method_using.call(column)
30
+ when Symbol
31
+ self.send(self.shard_method_using, column, self.shard_args)
32
+ end
33
+
34
+ constantize_class(i)
35
+ end
36
+
37
+ private
38
+
39
+ def mod(current, mod:)
40
+ current % mod
41
+ end
42
+
43
+ def constantize_class(i)
44
+ class_name = "#{base_class.name.demodulize}_%04d" % i
45
+ @@sharding_class ||= {}
46
+ @@sharding_class[class_name] ||= mutex.synchronize do
47
+ if module_name.const_defined?(class_name, false)
48
+ module_name.const_get(class_name, false)
49
+ else
50
+ Class.new(base_class) do
51
+ self.table_name = ("#{base_class.base_table_name}_%04d" % i)
52
+
53
+ if base_class.respond_to?(:protobuf_message)
54
+ self.protobuf_message base_class.protobuf_message
55
+
56
+ # Create a .to_proto method on XXX::ActiveRecord_Relation
57
+ self.const_get('ActiveRecord_Relation', false).class_exec do
58
+ def to_proto(*args)
59
+ msg_class = base_class.name.demodulize.pluralize
60
+ module_name = base_class.name.deconstantize.constantize
61
+ module_name::Messages.const_get(msg_class, false).new(msg_class.underscore => map { |r| r.to_proto(*args) })
62
+ end
63
+ end
64
+ end
65
+
66
+ end.tap { |k| module_name.const_set(class_name, k) }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,3 +1,3 @@
1
1
  module ActsAsShardable
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_shardable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - scorix
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-05-17 00:00:00.000000000 Z
11
+ date: 2019-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -97,6 +97,8 @@ files:
97
97
  - bin/console
98
98
  - bin/setup
99
99
  - lib/acts_as_shardable.rb
100
+ - lib/acts_as_shardable/attribute_methods.rb
101
+ - lib/acts_as_shardable/shard.rb
100
102
  - lib/acts_as_shardable/version.rb
101
103
  homepage: https://github.com/scorix
102
104
  licenses:
@@ -119,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
121
  version: '0'
120
122
  requirements: []
121
123
  rubyforge_project:
122
- rubygems_version: 2.6.11
124
+ rubygems_version: 2.7.3
123
125
  signing_key:
124
126
  specification_version: 4
125
127
  summary: Let subclasses of ActiveRecord::Base to be shardable.