ssdb-attr 0.0.11 → 0.1.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
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