ssdb-attr 0.0.11 → 0.1.0

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: 31499311caa45d7129e0a6c0d79a391db919ec93
4
- data.tar.gz: b345e7431fa1263c4f5c714a76fb23b762443a3a
3
+ metadata.gz: b2b52304c9d46396222d910c03ab4a3eb12d0210
4
+ data.tar.gz: 036af4b696a6ab5b733c71d01de9447303000ba7
5
5
  SHA512:
6
- metadata.gz: 3b164bab058e911c7bd893c70c07f0907f966f5e9d074f18df619443813e3b77ea04c7d461a1c623baca0514d5c65863e7a930e37034e595dc7e12410b526f2a
7
- data.tar.gz: 3a319604d98f756e0f0ab569aeff4f03a5e5d6a60b862d4ab2a2dc3456e2531384a2f30156a06dcd6e1a0813f82add9ee4b4332b892ceeae3cf45a44de863ec1
6
+ metadata.gz: bcee95b48f327fac5b65c582c89539c2c45a11e88b136e8084a229d5ed898e93bf4d1c0f472b05a68827acffb9ae25fd4a33f3ff04a4fcaf2972efb83305e2df
7
+ data.tar.gz: f66d638aead72c1de92caf614bb03d8d07c6358f59967fa9bb1c3b1835982fa26fd04c24d2922422506b15a900c46c3defcfc964983416aaa4b15f28371ee3c7
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.1.2
1
+ ruby-2.3.0
data/Gemfile CHANGED
@@ -1,5 +1,4 @@
1
- # source 'https://rubygems.org'
2
- source 'https://ruby.taobao.org'
1
+ source 'https://gems.ruby-china.org'
3
2
 
4
3
  # Specify your gem's dependencies in ssdb-attr.gemspec
5
4
  gemspec
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ssdb-attr (0.0.10)
4
+ ssdb-attr (0.0.11)
5
5
  activesupport (~> 4.2.1)
6
6
  connection_pool (~> 2.2.0)
7
7
  redis (~> 3.2.1)
8
8
 
9
9
  GEM
10
- remote: https://ruby.taobao.org/
10
+ remote: https://gems.ruby-china.org/
11
11
  specs:
12
12
  activemodel (4.2.1)
13
13
  activesupport (= 4.2.1)
@@ -26,12 +26,33 @@ GEM
26
26
  tzinfo (~> 1.1)
27
27
  arel (6.0.0)
28
28
  builder (3.2.2)
29
+ coderay (1.1.1)
29
30
  connection_pool (2.2.0)
31
+ diff-lcs (1.2.5)
30
32
  i18n (0.7.0)
31
33
  json (1.8.2)
34
+ method_source (0.8.2)
32
35
  minitest (5.5.1)
36
+ pry (0.10.4)
37
+ coderay (~> 1.1.0)
38
+ method_source (~> 0.8.1)
39
+ slop (~> 3.4)
33
40
  rake (10.4.2)
34
- redis (3.2.1)
41
+ redis (3.2.2)
42
+ rspec (3.5.0)
43
+ rspec-core (~> 3.5.0)
44
+ rspec-expectations (~> 3.5.0)
45
+ rspec-mocks (~> 3.5.0)
46
+ rspec-core (3.5.4)
47
+ rspec-support (~> 3.5.0)
48
+ rspec-expectations (3.5.0)
49
+ diff-lcs (>= 1.2.0, < 2.0)
50
+ rspec-support (~> 3.5.0)
51
+ rspec-mocks (3.5.0)
52
+ diff-lcs (>= 1.2.0, < 2.0)
53
+ rspec-support (~> 3.5.0)
54
+ rspec-support (3.5.0)
55
+ slop (3.6.0)
35
56
  sqlite3 (1.3.10)
36
57
  thread_safe (0.3.5)
37
58
  tzinfo (1.2.2)
@@ -44,7 +65,11 @@ DEPENDENCIES
44
65
  activerecord (~> 4.2.1)
45
66
  activerecord-nulldb-adapter
46
67
  bundler (~> 1.5)
47
- minitest (~> 5.5.1)
68
+ pry
48
69
  rake
70
+ rspec (~> 3.5.0)
49
71
  sqlite3
50
72
  ssdb-attr!
73
+
74
+ BUNDLED WITH
75
+ 1.13.6
data/Rakefile CHANGED
@@ -1,7 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
- require 'rake/testtask'
3
2
 
4
- Rake::TestTask.new do |t|
5
- t.libs << 'test'
6
- t.pattern = "test/**/*_test.rb"
3
+ desc "run all the specs"
4
+ task :test do
5
+ sh "rspec spec"
7
6
  end
7
+ task :default => :test
8
+ task :spec => :test
data/lib/ssdb-attr.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require "redis"
2
2
  require "connection_pool"
3
+ require 'active_support'
4
+ require "active_support/core_ext"
3
5
  require "active_support/concern"
4
6
  require "active_support/inflector"
5
7
  require "ssdb-attr/version"
@@ -7,19 +9,82 @@ require "ssdb/attr"
7
9
 
8
10
  module SSDBAttr
9
11
  class << self
10
- attr_accessor :pool
12
+ attr_accessor :pools
13
+ attr_accessor :default_pool_name
11
14
 
12
- def setup(options={})
13
- pool_size = (options[:pool] || 1).to_i
14
- timeout = (options[:timeout] || 2).to_i
15
+ #
16
+ # Globally setup SSDBAttr
17
+ #
18
+ # This method will setup the connecton pool to SSDB server.
19
+ #
20
+ # You should pass following values in `options` hash:
21
+ #
22
+ # `url/host+port`: To locate the SSDB server, `url` takes precedence.
23
+ # `pool`: Pool size of the connection pool. Default to 1.
24
+ # `timeout`: Timeout of the connection pool, in second, Default to 2.
25
+ #
26
+ # @param [Hash] options
27
+ #
28
+ # @return [void]
29
+ #
30
+ def setup(configuration)
31
+ raise "SSDB-Attr could not initialize!" if configuration.nil?
15
32
 
16
- SSDBAttr.pool = ConnectionPool.new(size: pool_size, timeout: timeout) do
17
- if options[:url].present?
18
- Redis.new(url: options[:url])
19
- else
20
- Redis.new(host: options[:host], port: options[:port])
33
+ SSDBAttr.pools = {}
34
+
35
+ if configuration.is_a?(Hash)
36
+ # Only one and the default connection pool.
37
+ conf = configuration.symbolize_keys
38
+
39
+ pool_name = conf[:name] || :default
40
+
41
+ SSDBAttr.pools[pool_name.to_sym] = create_pool(configuration)
42
+ SSDBAttr.default_pool_name = pool_name
43
+ end
44
+
45
+ if configuration.is_a?(Array)
46
+ # Multiple connection pools
47
+ configuration.each do |c|
48
+ conf = c.symbolize_keys
49
+
50
+ pool_name = conf[:name]
51
+
52
+ raise "ssdb-attr: Pool name not specified!" if pool_name.blank?
53
+
54
+ SSDBAttr.pools[pool_name.to_sym] = create_pool(conf)
55
+ SSDBAttr.default_pool_name = pool_name if conf[:default]
21
56
  end
22
57
  end
58
+
59
+ raise "ssdb-attr: No default pool in configuration!" if SSDBAttr.pool.nil?
60
+ end
61
+
62
+ def pool(name=nil)
63
+ name = name || SSDBAttr.default_pool_name
64
+ SSDBAttr.pools[name.to_sym]
65
+ end
66
+
67
+ def default_pool
68
+ SSDBAttr.pools[SSDBAttr.default_pool_name]
69
+ end
70
+
71
+ def create_pool(pool_options)
72
+ defaults = { :pool_size => 1, :timeout => 1 }
73
+
74
+ options = pool_options.reverse_merge(defaults).deep_symbolize_keys
75
+
76
+ ConnectionPool.new(:size => options[:pool_size], :timeout => options[:timeout]) do
77
+ create_conn(options)
78
+ end
23
79
  end
80
+
81
+ def create_conn(conn_options)
82
+ if !conn_options[:url].nil?
83
+ Redis.new(:url => conn_options[:url])
84
+ else
85
+ Redis.new(:host => conn_options[:host], :port => conn_options[:port])
86
+ end
87
+ end
88
+
24
89
  end
25
90
  end
@@ -1,3 +1,3 @@
1
1
  module SSDBAttr
2
- VERSION = "0.0.11"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/ssdb/attr.rb CHANGED
@@ -3,107 +3,167 @@ module SSDB
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- define_model_callbacks :update_ssdb_attrs, only: [:before, :after]
7
- after_create :init_ssdb_attrs
8
- after_destroy :clear_ssdb_attrs
6
+ instance_variable_set(:@ssdb_attr_names, [])
7
+
8
+ after_commit :save_ssdb_attrs, on: %i(create update)
9
+ after_commit :clear_ssdb_attrs, on: :destroy
9
10
  end
10
11
 
11
- def update_ssdb_attrs(attributes)
12
- # Determine what attrs are requested to be updated
13
- attributes = attributes.symbolize_keys
14
- attr_names = attributes.keys & self.class.ssdb_attr_names
12
+ module ClassMethods
13
+ attr_reader :ssdb_attr_names
14
+ attr_reader :ssdb_attr_id_field
15
+ attr_reader :ssdb_attr_pool_name
15
16
 
16
- # Determine dirty fields
17
- attr_names.each do |name|
18
- send("#{name}_will_change!") unless attributes[name] == send(name)
17
+ #
18
+ # 设置获取 SSDB Attr Id 的方式
19
+ #
20
+ # @param [String/Symbol] field_name
21
+ #
22
+ # @return [String]
23
+ #
24
+ def ssdb_attr_id(field_name)
25
+ raise if field_name.nil?
26
+ @ssdb_attr_id_field = field_name
19
27
  end
20
28
 
21
- run_callbacks :update_ssdb_attrs do
22
- SSDBAttr.pool.with do |conn|
23
- attr_names.each { |name| send("#{name}=", attributes[name]) }
24
- end
29
+ def ssdb_attr_pool(pool_name)
30
+ @ssdb_attr_pool_name = pool_name
25
31
  end
26
32
 
27
- # Clear dirty fields
28
- clear_attribute_changes(attr_names)
33
+ #
34
+ # Method to define a SSDB attribute in a Ruby Class
35
+ #
36
+ # @param [String/Symbol] name Attribute name.
37
+ # @param [String/Symbol] type Attribute type, now supports: string/integer
38
+ # @param [options] options Extra options.
39
+ #
40
+ # @return [void]
41
+ #
42
+ def ssdb_attr(name, type, options = {})
43
+ unless %i(string integer).include?(type)
44
+ raise "Type not supported, only `:string` and `:integer` are supported now."
45
+ end
29
46
 
30
- true # always return true
31
- end
47
+ @ssdb_attr_names << name.to_s
48
+
49
+ define_method(name) do
50
+ instance_variable_get("@#{name}") || begin
51
+ val = ssdb_attr_pool.with { |conn| conn.get(ssdb_attr_key(name)) } || options[:default]
52
+ instance_variable_set("@#{name}", val)
53
+ end
54
+ typecaster(instance_variable_get("@#{name}"), type)
55
+ end
56
+
57
+ define_method("#{name}=") do |val|
58
+ send("#{name}_will_change!") unless typecaster(val, type) == send(name)
59
+ instance_variable_set("@#{name}", val)
60
+ end
61
+
62
+ define_method("#{name}_was") { attribute_was(name) }
63
+
64
+ define_method("#{name}_change") { attribute_change(name) }
65
+
66
+ define_method("#{name}_changed?") { attribute_changed?(name) }
67
+
68
+ define_method("restore_#{name}!") { restore_attribute!(name) }
69
+
70
+ define_method("#{name}_will_change!") { attribute_will_change!(name) }
32
71
 
33
- def init_ssdb_attrs
34
- self.class.ssdb_attr_names.each do |attribute|
35
- SSDBAttr.pool.with { |conn| conn.set(to_ssdb_attr_key(attribute), self.send(attribute)) }
36
72
  end
37
73
  end
38
74
 
39
- def clear_ssdb_attrs
40
- self.class.ssdb_attr_names.each do |attribute|
41
- SSDBAttr.pool.with { |conn| conn.del(to_ssdb_attr_key(attribute)) }
75
+ #
76
+ # Overwrite `reload` method in ActiveRecord to reload SSDB attributes as well.
77
+ #
78
+ #
79
+ # @return [void]
80
+ #
81
+ def reload
82
+ super.tap do
83
+ reload_ssdb_attrs
42
84
  end
43
85
  end
44
86
 
45
- def to_ssdb_attr_key(name)
46
- klass = self.class
87
+ private
47
88
 
48
- custom_id = klass.instance_variable_get("@ssdb_attr_id")
89
+ def ssdb_attr_pool
90
+ SSDBAttr.pool(self.class.ssdb_attr_pool_name)
91
+ end
49
92
 
50
- if custom_id.present?
51
- "#{klass.name.tableize}:#{self.send(custom_id)}:#{name}"
93
+ #
94
+ # Cast the value from SSDB to the correct type.
95
+ #
96
+ # @param [Any] val Any value taken from SSDB Server.
97
+ # @param [String/Symbol] type Target value to cast to.
98
+ #
99
+ # @return [Any]
100
+ #
101
+ def typecaster(val, type)
102
+ case type.to_sym
103
+ when :string then val.to_s
104
+ when :integer then val.to_i
52
105
  else
53
- "#{klass.name.tableize}:#{id}:#{name}"
106
+ raise "Typecaster: i don't know this type: #{type}."
54
107
  end
55
108
  end
56
109
 
57
- private
110
+ def ssdb_attr_id
111
+ send(self.class.ssdb_attr_id_field || :id)
112
+ end
113
+
58
114
 
59
- def touch_db_column(names)
60
- names == true ? touch : touch(*names)
115
+ #
116
+ # Return the SSDB key for a attribute
117
+ #
118
+ # @param [String] name Attribute name.
119
+ #
120
+ # @return [String]
121
+ #
122
+ def ssdb_attr_key(name)
123
+ "#{self.class.name.tableize}:#{ssdb_attr_id}:#{name}"
61
124
  end
62
125
 
63
- module ClassMethods
64
- def ssdb_attr_names
65
- @ssdb_attr_names ||= []
126
+ #
127
+ # Delete all SSDB Attributes of current object in the server.
128
+ #
129
+ #
130
+ # @return [void]
131
+ #
132
+ def clear_ssdb_attrs
133
+ ssdb_attr_pool.with do |conn|
134
+ self.class.ssdb_attr_names.each { |attr| conn.del(ssdb_attr_key(attr)) }
66
135
  end
136
+ end
67
137
 
68
- def ssdb_attr_id(sym)
69
- @ssdb_attr_id = sym
138
+ #
139
+ # Save changed SSDb Attributes to the server.
140
+ #
141
+ #
142
+ # @return [void]
143
+ #
144
+ def save_ssdb_attrs
145
+ params = (previous_changes.keys & self.class.ssdb_attr_names).map do |attr|
146
+ ["#{ssdb_attr_key(attr)}", previous_changes[attr][1]]
70
147
  end
71
148
 
72
- # ssdb_attr :content, :string, default: 0, touch: true
73
- # ssdb_attr :writer_version, :integer, default: 0, touch: [:field1, :field2, :field3]
74
- #
75
- # [counter description]
76
- # @param name [type] [description]
77
- # @param name [type] [description]
78
- # @param options={} [type] [description]
79
- # @param block [description]
80
- #
81
- # @return [type] [description]
82
- def ssdb_attr(name, type, options={})
83
- unless [:string, :integer].include?(type)
84
- raise "Type not supported, only `:string` and `:integer` are supported now."
85
- end
86
-
87
- self.ssdb_attr_names << name
88
-
89
- define_method(name) do
90
- conversion = type == :string ? :to_s : :to_i
91
- value = SSDBAttr.pool.with { |conn| conn.get("#{to_ssdb_attr_key(name)}") }
92
- (value || options[:default]).send(conversion)
93
- end
94
-
95
- define_method("#{name}=") do |val|
96
- SSDBAttr.pool.with { |conn| conn.set("#{to_ssdb_attr_key(name)}", val) }
97
- touch_db_column(options[:touch]) if options[:touch].present?
98
- end
99
-
100
- define_method("#{name}_will_change!") do
101
- attribute_will_change!(name)
102
- end
149
+ ssdb_attr_pool.with do |conn|
150
+ conn.mset(*params.flatten)
151
+ end if params.length > 0
152
+ end
103
153
 
104
- define_method("#{name}_changed?") do
105
- attribute_changed?(name)
106
- end
154
+ #
155
+ # Reload attribute values from the server.
156
+ #
157
+ # This method will overwrite current changed but not saved values in the object.
158
+ #
159
+ #
160
+ # @return [void]
161
+ #
162
+ def reload_ssdb_attrs
163
+ values = ssdb_attr_pool.with { |conn| conn.mget(*self.class.ssdb_attr_names) }
164
+
165
+ self.class.ssdb_attr_names.each_with_index do |attr, index|
166
+ instance_variable_set("@#{attr}", values[index])
107
167
  end
108
168
  end
109
169
  end
data/spec/spec_helper.rb CHANGED
@@ -1,16 +1,20 @@
1
- require 'active_record'
2
- require 'nulldb'
3
- require 'ssdb-attr'
4
- require 'minitest/spec'
5
- require 'minitest/autorun'
6
- require 'minitest/mock'
7
- require 'minitest/pride'
1
+ require "active_record"
2
+ require "nulldb"
3
+ require "ssdb-attr"
8
4
 
9
- SSDBAttr.setup url: 'redis://localhost:6379/15'
5
+ # Setup ActiveRecord
6
+ ActiveRecord::Base.raise_in_transactional_callbacks = true if ActiveRecord::VERSION::STRING >= "4.2"
7
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
10
8
 
11
- # Setup activerecord
12
- ActiveRecord::Base.raise_in_transactional_callbacks = true if ActiveRecord::VERSION::STRING >= '4.2'
9
+ # Setup tables for test
10
+ tbls = [
11
+ { "posts" => "updated_at DATETIME, saved_at DATETIME, changed_at DATETIME" },
12
+ { "custom_id_fields" => "uuid VARCHAR" },
13
+ { "custom_pool_names" => "uuid VARCHAR" }
14
+ ]
13
15
 
14
- ActiveRecord::Base.establish_connection :adapter => 'sqlite3', database: ':memory:'
15
-
16
- ActiveRecord::Base.connection.execute "CREATE TABLE posts (id INTEGER NOT NULL PRIMARY KEY, updated_at DATETIME, content_updated_at DATETIME, title_updated_at DATETIME)"
16
+ tbls.each do |tbl|
17
+ tbl.each do |tbl_name, sql|
18
+ ActiveRecord::Base.connection.execute "CREATE TABLE #{tbl_name} (id INTEGER NOT NULL PRIMARY KEY, #{sql})"
19
+ end
20
+ end
@@ -1,9 +1,131 @@
1
- require 'spec_helper'
1
+ # require 'spec_helper'
2
+ require "ssdb-attr"
2
3
 
3
4
  describe SSDBAttr do
5
+
6
+ describe "#pool" do
7
+ it "should fetch the named pool if a connection name is passed" do
8
+ options = { :url => "redis://localhost:8888" }
9
+
10
+ SSDBAttr.setup(options)
11
+
12
+ pool_dbl = double(ConnectionPool)
13
+
14
+ expect(SSDBAttr.pools).to receive(:[]).with(:foo).and_return(pool_dbl)
15
+ expect(SSDBAttr.pool(:foo)).to eq(pool_dbl)
16
+ end
17
+
18
+ it "should return the default pool if connection name of nil is passed" do
19
+ options = { :url => "redis://localhost:8888" }
20
+
21
+ SSDBAttr.setup(options)
22
+
23
+ pool_dbl = double(ConnectionPool)
24
+
25
+ SSDBAttr.default_pool_name = :default_foo
26
+
27
+ expect(SSDBAttr.pools).to receive(:[]).with(:default_foo).and_return(pool_dbl)
28
+ expect(SSDBAttr.pool).to eq(pool_dbl)
29
+ end
30
+ end
31
+
4
32
  describe "#setup" do
5
- it "should setup a ssdb connection pool" do
6
- SSDBAttr.pool.wont_be_nil
33
+ context "with only one pool" do
34
+ it "should setup a ssdb connection pool with no name specified" do
35
+ options = { :url => "redis://localhost:8888" }
36
+
37
+ SSDBAttr.setup(options)
38
+
39
+ expect(SSDBAttr.pools.size).to eq(1)
40
+ expect(SSDBAttr.pools[:default]).not_to be_nil
41
+ expect(SSDBAttr.default_pool_name).to eq(:default)
42
+ end
43
+
44
+ it "should setup a ssdb connection pool with name specified" do
45
+ options = { :url => "redis://localhost:8888", :name => :main }
46
+
47
+ SSDBAttr.setup(options)
48
+
49
+ expect(SSDBAttr.pools.size).to eq(1)
50
+ expect(SSDBAttr.default_pool_name).to eq(:main)
51
+ expect(SSDBAttr.pools[:main]).not_to be_nil
52
+ expect(SSDBAttr.default_pool).to eq(SSDBAttr.pools[:main])
53
+ end
54
+ end
55
+
56
+ context "with pools" do
57
+ it "should raise error if no name specified" do
58
+ options = [
59
+ { :url => "redis://localhost:8888" },
60
+ { :url => "redis://localhost:6379" }
61
+ ]
62
+
63
+ expect { SSDBAttr.setup(options) }.to raise_error(RuntimeError)
64
+ end
65
+
66
+ it "should raise error if no default specified" do
67
+ options = [
68
+ { :url => "redis://localhost:8888", :name => :pool1 },
69
+ { :url => "redis://localhost:6379", :name => :pool2 }
70
+ ]
71
+
72
+ expect { SSDBAttr.setup(options) }.to raise_error(RuntimeError)
73
+ end
74
+
75
+ it "should initialize correctly" do
76
+ options = [
77
+ { :url => "redis://localhost:8888", :name => :ssdb, :pool_size => 10, :timeout => 2, :default => true },
78
+ { :url => "redis://localhost:6379", :name => :redis, :pool_size => 5, :timeout => 3 }
79
+ ]
80
+
81
+ SSDBAttr.setup(options)
82
+
83
+ expect(SSDBAttr.pools.size).to eq(2)
84
+ expect(SSDBAttr.pools[:ssdb]).to be_a(ConnectionPool)
85
+ expect(SSDBAttr.pools[:redis]).to be_a(ConnectionPool)
86
+ expect(SSDBAttr.default_pool_name).to eq(:ssdb)
87
+ expect(SSDBAttr.default_pool).to eq(SSDBAttr.pools[:ssdb])
88
+ end
89
+ end
90
+ end
91
+
92
+ describe "#create_pool" do
93
+ it "will use create a connection pool" do
94
+ pool = SSDBAttr.create_pool(:url => "redis://localhost:8888", :pool_size => 10, :timeout => 18)
95
+
96
+ expect(pool).not_to be_nil
97
+ expect(pool).to be_a(ConnectionPool)
98
+ expect(pool.instance_variable_get(:@size)).to eq(10)
99
+ expect(pool.instance_variable_get(:@timeout)).to eq(18)
100
+
101
+ conn = pool.with { |conn| conn }
102
+ expect(conn).to be_a(Redis)
103
+ expect(conn.client.host).to eq("localhost")
104
+ expect(conn.client.port).to eq(8888)
105
+ end
106
+ end
107
+
108
+ describe "#create_conn" do
109
+ context "with url" do
110
+ it do
111
+ conn = SSDBAttr.create_conn(:url => "redis://localhost:8888")
112
+
113
+ expect(conn).not_to be_nil
114
+ expect(conn).to be_a(Redis)
115
+ expect(conn.client.host).to eq("localhost")
116
+ expect(conn.client.port).to eq(8888)
117
+ end
118
+ end
119
+
120
+ context "with host/port options" do
121
+ it do
122
+ conn = SSDBAttr.create_conn(:host => "localhost", :port => "8888")
123
+
124
+ expect(conn).not_to be_nil
125
+ expect(conn).to be_a(Redis)
126
+ expect(conn.client.host).to eq("localhost")
127
+ expect(conn.client.port).to eq(8888)
128
+ end
7
129
  end
8
130
  end
9
131
  end
@@ -1,86 +1,232 @@
1
- require 'spec_helper'
1
+ require "spec_helper"
2
2
 
3
3
  class Post < ActiveRecord::Base
4
4
  include SSDB::Attr
5
5
 
6
- ssdb_attr :title, :string, touch: [:content_updated_at]
7
- ssdb_attr :content, :string, touch: true
8
- ssdb_attr :version, :integer, default: 22
9
- ssdb_attr :counter, :integer, default: 4
6
+ ssdb_attr :name, :string
7
+ ssdb_attr :int_version, :integer
8
+ ssdb_attr :default_title, :string, default: "Untitled"
9
+ ssdb_attr :title, :string
10
+ ssdb_attr :content, :string
11
+ ssdb_attr :version, :integer, default: 1
12
+ ssdb_attr :default_version, :integer, :default => 100
13
+ ssdb_attr :field_with_validation, :string
10
14
 
11
- before_update_ssdb_attrs :before_callback1, :before_callback2
12
- after_update_ssdb_attrs :after_callback1, :after_callback2
15
+ validate :validate_field
13
16
 
14
- def before_callback1
15
- puts "before callback 1"
16
-
17
- if title_changed?
18
- puts "title changed detected."
17
+ def validate_field
18
+ if field_with_validation == "foobar"
19
+ errors.add(:field_with_validation, "foobar error")
19
20
  end
20
21
  end
22
+ end
21
23
 
22
- def before_callback2
23
- puts "before callback 2"
24
- end
24
+ class CustomIdField < ActiveRecord::Base
25
+ include SSDB::Attr
25
26
 
26
- def after_callback1
27
- puts "after callback 1"
28
- end
27
+ ssdb_attr_id :uuid
28
+ ssdb_attr :content, :string
29
+ end
29
30
 
30
- def after_callback2
31
- puts "after callback 2"
32
- end
31
+ class CustomPoolName < ActiveRecord::Base
32
+ include SSDB::Attr
33
33
 
34
- # touch_ssdb_attr [:title, :content], touch: [:content_at]
35
- #
36
- # dummy.update_ssdb_attrs(title: 'abc', content: 'cbd')
37
- #
38
- # before_update_ssdb_attributes args*
39
- # after_update_ssdb_attributes :touch_updated_at
34
+ ssdb_attr_pool :foo_pool
35
+
36
+ ssdb_attr :foo_id, :integer
40
37
  end
41
38
 
42
39
  describe SSDB::Attr do
43
40
 
44
- describe "method created" do
41
+ before(:all) do
42
+ # Connect to test SSDB server
43
+ SSDBAttr.setup(:url => "redis://localhost:8888")
44
+
45
+ # Clean up SSDB
46
+ system('printf "7\nflushdb\n\n4\nping\n\n" | nc 127.0.0.1 8888 -i 1 > /dev/null')
47
+
48
+ ActiveRecord::Base.connection.tables.each do |table|
49
+ ActiveRecord::Base.connection.execute "DELETE FROM #{table}"
50
+ end
45
51
  end
46
52
 
47
- describe "#update_ssdb_attrs" do
48
- if 'should correctly set values'
49
- post = Post.create
53
+ context "Post" do
54
+ let(:post) { Post.create(updated_at: 1.day.ago, saved_at: 1.day.ago, changed_at: 1.day.ago) }
55
+ let(:redis) { Redis.new(:url => 'redis://localhost:8888') }
56
+
57
+ describe "@ssdb_attr_names" do
58
+ it "should set `@ssdb_attr_names` correctly" do
59
+ ssdb_attr_names = Post.instance_variable_get(:@ssdb_attr_names)
60
+ expect(ssdb_attr_names).to match_array(["name", "int_version", "default_title", "title",
61
+ "content", "version", "default_version", "field_with_validation"])
62
+ end
63
+ end
64
+
65
+ it "should respond to methods" do
66
+ expect(post.respond_to?(:name)).to be true
67
+ expect(post.respond_to?(:name=)).to be true
68
+ expect(post.respond_to?(:name_was)).to be true
69
+ expect(post.respond_to?(:name_change)).to be true
70
+ expect(post.respond_to?(:name_changed?)).to be true
71
+ expect(post.respond_to?(:restore_name!)).to be true
72
+ expect(post.respond_to?(:name_will_change!)).to be true
73
+ end
74
+
75
+ describe "#attribute=" do
76
+ it "shouldn't change the attribute value in SSDB" do
77
+ post.title = "foobar"
78
+ expect(redis.get(post.send(:ssdb_attr_key, :title))).not_to eq("foobar")
79
+ end
80
+
81
+ it "should change the attribute value" do
82
+ post.title = "foobar"
83
+ expect(post.title).to eq("foobar")
84
+ end
85
+
86
+ it "should track attirbute changes if value changed" do
87
+ expect(post).to receive(:title_will_change!)
88
+ post.title = "foobar"
89
+ end
90
+
91
+ it "shouldn't track attirbute changes unless value changed" do
92
+ expect(post).not_to receive(:title_will_change!)
93
+ post.title = ""
94
+ end
95
+ end
96
+
97
+ describe "#reload_ssdb_attrs" do
98
+ it "should reload attribute values from SSDB" do
99
+ post = Post.create(title: "foobar", version: 4)
100
+ post.title = "fizzbuzz"
101
+ post.version = 3
102
+
103
+ post.send(:reload_ssdb_attrs)
104
+ expect(post.title).to eq("foobar")
105
+ expect(post.version).to eq(4)
106
+ end
107
+ end
108
+
109
+ describe "#save_ssdb_attrs" do
110
+ it "should save attribute values in SSDB" do
111
+ allow(post).to receive(:previous_changes).and_return({"title"=>["", "foobar2"]})
112
+ post.send(:save_ssdb_attrs)
113
+ expect(redis.get("posts:#{post.id}:title")).to eq("foobar2")
114
+ end
115
+ end
116
+
117
+ describe "#clear_ssdb_attrs" do
118
+ before do
119
+ post.update(:title => "foobar2")
120
+ end
121
+
122
+ it "should remove attribute from ssdb" do
123
+ post.send(:clear_ssdb_attrs)
124
+ expect(redis.exists("posts:#{post.id}:title")).to be false
125
+ end
126
+ end
127
+
128
+ describe "#ssdb_attr_key" do
129
+ it "should return correct key" do
130
+ expect(post.send(:ssdb_attr_key, "name")).to eq("posts:#{post.id}:name")
131
+ end
132
+ end
133
+
134
+ context "type: :integer" do
135
+ it "default value should be 0" do
136
+ expect(post.int_version).to eq(0)
137
+ end
138
+
139
+ it "should hold integer value and return it" do
140
+ post.int_version = 4
141
+ expect(post.int_version).to eq(4)
142
+ end
50
143
 
51
- post.update_ssdb_attrs(title: 'bar2')
52
- post.update_ssdb_attrs(title: 'bar2')
144
+ it "default value with `:default` option" do
145
+ expect(post.default_version).to eq(100)
146
+ end
147
+ end
148
+
149
+ context "type: :string" do
150
+ it "default value" do
151
+ expect(post.title).to eq("")
152
+ end
153
+
154
+ it "default value with `:default` option" do
155
+ expect(post.default_title).to eq("Untitled")
156
+ end
157
+ end
158
+
159
+ context "callbacks" do
160
+ it "should save attribute values in SSDB when AR object save" do
161
+ post = Post.new
162
+ expect(post).to receive(:save_ssdb_attrs)
163
+ post.save
164
+ end
165
+
166
+ it "should clear attribute values in SSDB when AR object destroyed" do
167
+ expect(post).to receive(:clear_ssdb_attrs)
168
+ post.destroy
169
+ end
170
+ end
53
171
 
54
- # title = 'foo'
55
- # content = 'bar'
56
- #
57
- # title.must_equal 'foo'
58
- # content.must_equal 'bar'
59
- #
60
- #post.title.must_equal 'foo'
61
- #post.content.must_equal 'bar'
172
+ context "validation" do
173
+ it "should call the validation method" do
174
+ post.field_with_validation = "hellow world"
175
+ expect(post).to receive(:validate_field)
176
+ post.save
177
+ end
178
+
179
+ context "on validation passed" do
180
+ it do
181
+ post.field_with_validation = "hello world"
182
+
183
+ expect(post.save).to eq(true)
184
+ expect(redis.get("posts:#{post.id}:field_with_validation")).to eq("hello world")
185
+ expect(post.errors.empty?).to be_truthy
186
+ end
187
+ end
188
+
189
+ context "on validation failed" do
190
+ it "should not update the value in SSDB if validation fails" do
191
+ post.field_with_validation = "foobar"
192
+
193
+ expect(post.save).to eq(false)
194
+ expect(redis.get("posts:#{post.id}:field_with_validation")).not_to eq("foobar")
195
+ expect(post.errors.empty?).not_to be_truthy
196
+ expect(post.errors[:field_with_validation]).to eq(["foobar error"])
197
+ end
198
+ end
62
199
  end
63
200
  end
64
201
 
65
- describe "#ssdb_attr" do
66
- it 'should set default value correctly' do
67
- post = Post.create
202
+ context "CustomIdField" do
203
+ let(:custom_id_field) { CustomIdField.create(:uuid => 123) }
68
204
 
69
- post.counter.must_equal 4
205
+ it "should use the custom id correctly" do
206
+ expect(CustomIdField.instance_variable_get(:@ssdb_attr_id_field)).to eq(:uuid)
207
+ expect(custom_id_field.send(:ssdb_attr_key, "content")).to eq("custom_id_fields:123:content")
70
208
  end
209
+ end
71
210
 
72
- it "should convert to string correctly" do
73
- post = Post.create
211
+ context "CustomPoolName" do
74
212
 
75
- post.title = 120
76
- post.title.must_equal '120'
213
+ it "should respond to methods" do
214
+ expect(CustomPoolName).to respond_to(:ssdb_attr_pool)
77
215
  end
78
216
 
79
- it "should convert to integer correctly" do
80
- post = Post.create
217
+ it "should set SSDBAttr connection for class correct" do
218
+ expect(CustomPoolName.ssdb_attr_pool_name).to eq(:foo_pool)
219
+ end
220
+
221
+ describe ".ssdb_attr_pool" do
222
+ it do
223
+ ccn = CustomPoolName.new
224
+
225
+ pool_dbl = double(ConnectionPool)
81
226
 
82
- post.version = '120'
83
- post.version.must_equal 120
227
+ expect(SSDBAttr).to receive(:pool).with(:foo_pool).and_return(pool_dbl)
228
+ expect(ccn.send(:ssdb_attr_pool)).to eq(pool_dbl)
229
+ end
84
230
  end
85
231
  end
86
232
  end
data/ssdb-attr.gemspec CHANGED
@@ -25,7 +25,8 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency "bundler", "~> 1.5"
26
26
  spec.add_development_dependency "rake"
27
27
  spec.add_development_dependency "sqlite3"
28
- spec.add_development_dependency "minitest", "~> 5.5.1"
28
+ spec.add_development_dependency "pry"
29
+ spec.add_development_dependency "rspec", "~> 3.5.0"
29
30
  spec.add_development_dependency "activerecord", "~> 4.2.1"
30
31
  spec.add_development_dependency "activerecord-nulldb-adapter"
31
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ssdb-attr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Larry Zhao
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-26 00:00:00.000000000 Z
11
+ date: 2016-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -95,19 +95,33 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: minitest
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
115
  - - "~>"
102
116
  - !ruby/object:Gem::Version
103
- version: 5.5.1
117
+ version: 3.5.0
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
122
  - - "~>"
109
123
  - !ruby/object:Gem::Version
110
- version: 5.5.1
124
+ version: 3.5.0
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: activerecord
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -144,6 +158,7 @@ extensions: []
144
158
  extra_rdoc_files: []
145
159
  files:
146
160
  - ".gitignore"
161
+ - ".rspec"
147
162
  - ".ruby-gemset"
148
163
  - ".ruby-version"
149
164
  - Gemfile
@@ -157,7 +172,6 @@ files:
157
172
  - spec/ssdb-attr_spec.rb
158
173
  - spec/ssdb/attr_spec.rb
159
174
  - ssdb-attr.gemspec
160
- - test/ssdb_attr_test.rb
161
175
  homepage: ''
162
176
  licenses:
163
177
  - MIT
@@ -178,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
192
  version: '0'
179
193
  requirements: []
180
194
  rubyforge_project:
181
- rubygems_version: 2.2.2
195
+ rubygems_version: 2.6.7
182
196
  signing_key:
183
197
  specification_version: 4
184
198
  summary: Provide simple helpers on persist values in redis for performance. Works
@@ -187,4 +201,3 @@ test_files:
187
201
  - spec/spec_helper.rb
188
202
  - spec/ssdb-attr_spec.rb
189
203
  - spec/ssdb/attr_spec.rb
190
- - test/ssdb_attr_test.rb
@@ -1,161 +0,0 @@
1
- require 'ssdb-attr'
2
- require 'active_record'
3
- ActiveRecord::Base.raise_in_transactional_callbacks = true if ActiveRecord::VERSION::STRING >= '4.2'
4
-
5
- require 'minitest/autorun'
6
- require 'minitest/spec'
7
- require 'minitest/pride'
8
- test_framework = defined?(MiniTest::Test) ? MiniTest::Test : MiniTest::Unit::TestCase
9
-
10
- require File.expand_path(File.dirname(__FILE__) + "/../lib/ssdb/attr")
11
-
12
- def connect!
13
- SSDBAttr.setup(url: 'redis://localhost:6379/15')
14
- ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
15
- end
16
-
17
- def setup!
18
- connect!
19
- { 'posts' => 'updated_at DATETIME, saved_at DATETIME, changed_at DATETIME' }.each do |table_name, columns_as_sql_string|
20
- ActiveRecord::Base.connection.execute "CREATE TABLE #{table_name} (id INTEGER NOT NULL PRIMARY KEY, #{columns_as_sql_string})"
21
- end
22
-
23
- { 'chat_messages' => 'uuid VARCHAR' }.each do |table_name, columns_as_sql_string|
24
- ActiveRecord::Base.connection.execute "CREATE TABLE #{table_name} (id INTEGER NOT NULL PRIMARY KEY, #{columns_as_sql_string})"
25
- end
26
- end
27
-
28
- setup!
29
-
30
- class Post < ActiveRecord::Base
31
- include SSDB::Attr
32
-
33
- ssdb_attr :name, :string
34
- ssdb_attr :int_version, :integer
35
- ssdb_attr :default_title, :string, default: "Untitled"
36
- ssdb_attr :plain_touch, :string, touch: true
37
- ssdb_attr :custom_touch_single_column, :string, touch: :saved_at
38
- ssdb_attr :custom_touch_multiple_columns, :string, touch: [:saved_at, :changed_at]
39
- ssdb_attr :title, :string
40
- ssdb_attr :content, :string
41
- ssdb_attr :version, :integer, default: 1
42
-
43
- before_update_ssdb_attrs :before1, :before2
44
- after_update_ssdb_attrs :after1, :after2
45
-
46
- def callback_out
47
- @callback_out ||= []
48
- end
49
-
50
- [:before1, :before2, :after1, :after2].each do |name|
51
- define_method(name) do
52
- callback_out << name
53
- end
54
- end
55
- end
56
-
57
- class ChatMessage < ActiveRecord::Base
58
- include SSDB::Attr
59
-
60
- ssdb_attr :content, :string
61
- ssdb_attr_id :uuid
62
- end
63
-
64
- class SsdbAttrTest < test_framework
65
- def setup
66
- SSDBAttr.pool.with { |conn| conn.flushdb }
67
- ActiveRecord::Base.connection.tables.each do |table|
68
- ActiveRecord::Base.connection.execute "DELETE FROM #{table}"
69
- end
70
- @post = Post.create(updated_at: 1.day.ago, saved_at: 1.day.ago, changed_at: 1.day.ago)
71
- @chat_message = ChatMessage.create(uuid: SecureRandom.uuid)
72
- end
73
-
74
- def test_respond_to_methods
75
- assert_equal true, @post.respond_to?(:name)
76
- assert_equal true, @post.respond_to?(:name=)
77
- assert_equal true, @post.respond_to?(:name_changed?)
78
- assert_equal true, @post.respond_to?(:name_will_change!)
79
- end
80
-
81
- def test_integer_attribute
82
- @post.int_version = "4"
83
- assert_equal 4, @post.int_version
84
- end
85
-
86
- def test_with_default_value
87
- assert_equal "Untitled", @post.default_title
88
- end
89
-
90
- def test_with_plain_touch
91
- original_updated_at = @post.updated_at
92
- @post.plain_touch = "something"
93
- refute_equal original_updated_at, @post.updated_at
94
- end
95
-
96
- def test_with_custom_touch_signle_column
97
- original_saved_at = @post.saved_at
98
- @post.custom_touch_single_column = "something"
99
- refute_equal original_saved_at, @post.saved_at
100
- end
101
-
102
- def test_with_custom_touch_multiple_columns
103
- original_saved_at, original_changed_at = @post.saved_at, @post.changed_at
104
- @post.custom_touch_multiple_columns = "something"
105
- refute_equal original_saved_at, @post.saved_at
106
- refute_equal original_changed_at, @post.changed_at
107
- end
108
-
109
- def test_to_ssdb_attr_key
110
- assert_equal "posts:#{@post.id}:name", @post.to_ssdb_attr_key("name")
111
- end
112
-
113
- def test_custom_ssdb_attr_id
114
- assert_equal "chat_messages:#{@chat_message.uuid}:content", @chat_message.to_ssdb_attr_key("content")
115
- end
116
-
117
- def test_update_ssdb_attrs_with_symbolize_keys
118
- @post.update_ssdb_attrs(title: "note one", content: "testing!!!", version: 1)
119
- assert_equal "note one", @post.title
120
- assert_equal "testing!!!", @post.content
121
- assert_equal 1, @post.version
122
- end
123
-
124
- def test_update_ssdb_attrs_with_string_keys
125
- @post.update_ssdb_attrs("title" => "note one", "content" => "testing!!!", "version" => 1)
126
- assert_equal "note one", @post.title
127
- assert_equal "testing!!!", @post.content
128
- assert_equal 1, @post.version
129
- end
130
-
131
- def test_update_ssdb_attrs_on_object_return_true
132
- assert_equal true, @post.update_ssdb_attrs(title: "note one", content: "testing!!!", version: 1)
133
- end
134
-
135
- def test_update_ssdb_attrs_callbacks
136
- @post.update_ssdb_attrs(title: "something")
137
- assert_equal [:before1, :before2, :after1, :after2], @post.callback_out
138
- end
139
-
140
- def test_object_destroy_callbacks
141
- @post.update_ssdb_attrs(title: "note one", content: "nice job!")
142
- ssdb_title_key = @post.to_ssdb_attr_key(:title)
143
- ssdb_content_key = @post.to_ssdb_attr_key(:content)
144
- assert_equal true, SSDBAttr.pool.with { |conn| conn.exists(ssdb_title_key) }
145
- assert_equal true, SSDBAttr.pool.with { |conn| conn.exists(ssdb_content_key) }
146
- @post.destroy
147
- assert_equal false, SSDBAttr.pool.with { |conn| conn.exists(ssdb_title_key) }
148
- assert_equal false, SSDBAttr.pool.with { |conn| conn.exists(ssdb_content_key) }
149
- end
150
-
151
- def test_object_create_callbacks
152
- title_key = @post.to_ssdb_attr_key(:title)
153
- default_title_key = @post.to_ssdb_attr_key(:default_title)
154
- version_key = @post.to_ssdb_attr_key(:version)
155
-
156
- assert_equal 10, SSDBAttr.pool.with { |conn| conn.keys.count }
157
- assert_equal true, SSDBAttr.pool.with { |conn| conn.exists(title_key) }
158
- assert_equal "Untitled", SSDBAttr.pool.with { |conn| conn.get(default_title_key) }
159
- assert_equal "1", SSDBAttr.pool.with { |conn| conn.get(version_key) }
160
- end
161
- end