redis-attrs 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Redis::Attrs - Add attributes to Ruby classes backed by Redis
2
2
 
3
+ [![Code Climate](https://codeclimate.com/github/gnapse/redis-attrs.png)](https://codeclimate.com/github/gnapse/redis-attrs)
4
+
3
5
  This gem is an amalgamation of the ideas found within the [redis_props][redis_props]
4
6
  and [redis-objects][redis-objects] gems, plus a few new ideas here and there. It
5
7
  provides a way to define, on any Ruby class, some attributes that are backed by
@@ -33,47 +35,65 @@ Or install it yourself as:
33
35
 
34
36
  $ gem install redis-attrs
35
37
 
38
+ ## Setting up the connection
39
+
40
+ You can include some of the following code snippets at the beginning of your
41
+ app or script. In case you're using Rails, you can use an initializer.
42
+
43
+ ```ruby
44
+ # Standard connection
45
+ Redis::Attrs.redis = Redis.new
46
+
47
+ # Connection with specific parameters
48
+ Redis::Attrs.redis = Redis.new(host: 'hostname', port: 8888, password: 'secret')
49
+
50
+ # You can even use a redis namespace
51
+ Redis::Attrs.redis = Redis::Namespace.new("blah", redis: Redis.new)
52
+ ```
53
+
36
54
  ## Usage
37
55
 
38
56
  Start by defining some attributes on your class:
39
57
 
40
- class Film
41
- include Redis::Attrs
42
- redis_attrs :title => :string, :length => :integer
43
- redis_attrs :released_on => :date, :cast => :list
58
+ ```ruby
59
+ class Film
60
+ include Redis::Attrs
61
+ redis_attrs :title => :string, :length => :integer
62
+ redis_attrs :released_on => :date, :cast => :list
44
63
 
45
- # Remember that the objects need an id for this to work
46
- attr_reader :id
47
- def initialize(id)
48
- @id = id
49
- end
64
+ # Remember that the objects need an id for this to work
65
+ attr_reader :id
66
+ def initialize(id)
67
+ @id = id
68
+ end
50
69
 
51
- def presentation_title
52
- "#{title} (#{released_on.year})"
53
- end
54
- end
70
+ def presentation_title
71
+ "#{title} (#{released_on.year})"
72
+ end
73
+ end
74
+ ```
55
75
 
56
76
  Then you can use those attributes as you would regularly, but internally they are
57
77
  reading from and writing to Redis.
58
78
 
59
- >> film = Film.new(3)
60
- >> film.title = "Argo"
61
- >> film.released_on = "2012-10-12"
62
- >> puts film.presentation_title
63
- Argo (2012)
64
- >> puts film.cast.size
65
- 0
66
- >> film.cast = ["Ben Affleck", "Alan Arkin", "Brian Cranston"]
67
- >> puts film.cast.size
68
- 3
69
- >> puts film.cast[-3]
70
- Ben Affleck
79
+ ```ruby
80
+ >> film = Film.new(3)
81
+ >> film.title = "Argo"
82
+ >> film.released_on = "2012-10-12"
83
+ >> puts film.presentation_title
84
+ Argo (2012)
85
+ >> puts film.cast.size
86
+ 0
87
+ >> film.cast = ["Ben Affleck", "Alan Arkin", "Brian Cranston"]
88
+ >> puts film.cast.size
89
+ 3
90
+ >> puts film.cast[-3]
91
+ Ben Affleck
92
+ ```
71
93
 
72
94
  `Redis::Attrs` will work on _any_ class that provides an `id` method that returns
73
95
  a unique value. `Redis::Attrs` will automatically create keys that are unique to
74
- each object, in the format:
75
-
76
- class_name:id:attr_name
96
+ each object, in the format `class_name:id:attr_name`.
77
97
 
78
98
  ### Supported types
79
99
 
@@ -97,29 +117,33 @@ The `serialize` and `deserialize` methods define how this process is done. Afte
97
117
  registering the type with `Redis::Attrs`, a new attribute is added to the class
98
118
  `Film` defined above.
99
119
 
100
- class JSONScalar < Redis::Attrs::Scalar
101
- def serialize(value)
102
- value.to_json
103
- end
120
+ ```ruby
121
+ class JSONScalar < Redis::Attrs::Scalar
122
+ def serialize(value)
123
+ value.to_json
124
+ end
104
125
 
105
- def deserialize(value)
106
- JSON.parse(value)
107
- end
108
- end
126
+ def deserialize(value)
127
+ JSON.parse(value)
128
+ end
129
+ end
109
130
 
110
- Redis::Attrs.register_type(:json, JSONScalar)
131
+ Redis::Attrs.register_type(:json, JSONScalar)
111
132
 
112
- class Film
113
- redis_attrs :director => :json
114
- end
133
+ class Film
134
+ redis_attrs :director => :json
135
+ end
136
+ ```
115
137
 
116
138
  After the definitions above, more complex data structures could be stored as a single
117
139
  scalar value, by being serialized as JSON.
118
140
 
119
- >> film = Film.new(1)
120
- >> film.director = { "first_name" => "Ben", "last_name" => "Affleck" }
121
- >> puts Redis::Attrs.redis.get("film:1:director")
122
- {"first_name":"Ben","last_name":"Affleck"}
141
+ ```ruby
142
+ >> film = Film.new(1)
143
+ >> film.director = { "first_name" => "Ben", "last_name" => "Affleck" }
144
+ >> puts Redis::Attrs.redis.get("film:1:director")
145
+ {"first_name":"Ben","last_name":"Affleck"}
146
+ ```
123
147
 
124
148
  ### Attribute configuration options
125
149
 
@@ -127,8 +151,10 @@ The complex attribute types support some configuration options, mostly specific
127
151
  each type. When an attribute needs to be configured with some of these options, then
128
152
  it must be declared with the singular version of the method `redis_attrs`, like below:
129
153
 
130
- redis_attr :crawl, :lock, :expiration => 15.minutes
131
- redis_attr :cast, :list, :marshal => true
154
+ ```ruby
155
+ redis_attr :crawl, :lock, :expiration => 15.minutes
156
+ redis_attr :cast, :list, :marshal => true
157
+ ```
132
158
 
133
159
  For more details about the supported configuration options for each of the complex
134
160
  data types, please refer to the [redis-objects][redis-objects] gem.
@@ -139,16 +165,20 @@ There's an attribute configuration option for lists and sets, the `:filter` opti
139
165
  that allows the user to define a function that will modify the items upon insertion
140
166
  into the collection.
141
167
 
142
- class Film
143
- redis_attr :genres, :set, :filter => lambda { |v| v.strip.downcase.gsub(/\s+/, ' ') }
144
- end
168
+ ```ruby
169
+ class Film
170
+ redis_attr :genres, :set, :filter => lambda { |v| v.strip.downcase.gsub(/\s+/, ' ') }
171
+ end
172
+ ```
145
173
 
146
174
  After the above declaration we could do:
147
175
 
148
- >> film = Film.new(1)
149
- >> film.genres = ["Action ", " drama", "film Noir", "Drama", "Film noir "]
150
- >> puts film.genres.members.sort
151
- ["action", "drama", "film noir"]
176
+ ```ruby
177
+ >> film = Film.new(1)
178
+ >> film.genres = ["Action ", " drama", "film Noir", "Drama", "Film noir "]
179
+ >> puts film.genres.members.sort
180
+ ["action", "drama", "film noir"]
181
+ ```
152
182
 
153
183
  ## Contributing
154
184
 
@@ -5,11 +5,11 @@ require "active_support/inflector"
5
5
  class Redis
6
6
  module Attrs
7
7
  def self.redis
8
- @redis ||= Redis.new
8
+ @redis || $redis || Redis.current ||
9
+ raise(NotConnected, "Redis::Attrs.redis not set to a valid redis connection")
9
10
  end
10
11
 
11
12
  def self.redis=(r)
12
- raise ArgumentError, "Redis Attrs: Invalid Redis instance" unless r.is_a?(Redis)
13
13
  @redis = r
14
14
  end
15
15
 
@@ -19,7 +19,7 @@ class Redis
19
19
  end
20
20
 
21
21
  def redis_key_prefix
22
- @redis_key_refix ||= ActiveSupport::Inflector.underscore(self.name)
22
+ @redis_key_refix ||= ActiveSupport::Inflector.underscore(name)
23
23
  end
24
24
 
25
25
  def redis_attrs(attrs = nil)
@@ -32,7 +32,7 @@ class Redis
32
32
 
33
33
  def redis_attr(name, type, options = {})
34
34
  @redis_attrs ||= []
35
- klass = Redis::Attrs::supported_types[type]
35
+ klass = Redis::Attrs.supported_types[type]
36
36
  raise ArgumentError, "Unknown Redis::Attr type #{type}" if klass.nil?
37
37
  attr = klass.new(self, name, type, options)
38
38
  @redis_attrs << attr
@@ -73,7 +73,9 @@ class Redis
73
73
  def self.register_type(type, klass)
74
74
  type = type.to_sym
75
75
  raise ArgumentError, "Redis attr type #{type} is already defined" if supported_types.include?(type)
76
- raise ArgumentError, "Class implementing new type #{type} must be a subclass of Redis::Attrs::Scalar" unless klass.ancestors.include?(Scalar)
76
+ unless klass.ancestors.include?(Scalar)
77
+ raise ArgumentError, "Class implementing new type #{type} must be a subclass of Redis::Attrs::Scalar"
78
+ end
77
79
  @supported_types[type] = klass
78
80
  end
79
81
 
@@ -13,7 +13,7 @@ class Redis
13
13
  def initialize(klass, name, type, options)
14
14
  super
15
15
  attr = self
16
- attr_class = self.class.redis_object_class[self.type]
16
+ attr_class = self.class.redis_object_class[type]
17
17
 
18
18
  # Define the getter
19
19
  klass.send(:define_method, name) do
@@ -8,7 +8,7 @@ class Redis
8
8
  end
9
9
 
10
10
  # Add a member before or after pivot in the list. Redis: LINSERT
11
- def insert(where,pivot,value)
11
+ def insert(where, pivot, value)
12
12
  if options[:filter]
13
13
  value = options[:filter].call(value)
14
14
  pivot = options[:filter].call(pivot)
@@ -22,7 +22,7 @@ class Redis
22
22
  super
23
23
  end
24
24
 
25
- def delete(name, count=0)
25
+ def delete(name, count = 0)
26
26
  name = options[:filter].call(name) if options[:filter]
27
27
  super
28
28
  end
@@ -1,5 +1,5 @@
1
1
  class Redis
2
2
  module Attrs
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.1"
4
4
  end
5
5
  end
@@ -13,11 +13,12 @@ Gem::Specification.new do |gem|
13
13
  gem.homepage = "http://github.com/gnapse/redis-attrs"
14
14
 
15
15
  gem.files = `git ls-files`.split($/)
16
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
19
 
20
20
  gem.add_development_dependency "rspec"
21
+ gem.add_development_dependency "rake"
21
22
  gem.add_dependency "redis"
22
23
  gem.add_dependency "activesupport"
23
24
  gem.add_dependency "redis-objects"
@@ -26,37 +26,40 @@ describe Redis::Attrs do
26
26
  let(:film) { Film.new }
27
27
 
28
28
  it "has a version number" do
29
- Redis::Attrs::VERSION.should_not be_nil
29
+ expect(Redis::Attrs::VERSION).not_to be_nil
30
30
  end
31
31
 
32
32
  context "when included in a class" do
33
33
  it "makes the class respond to .redis_attrs" do
34
- Film.should respond_to(:redis_attrs)
34
+ expect(Film).to respond_to(:redis_attrs)
35
35
  end
36
36
 
37
37
  it "provides the class and its instances with a .redis interface" do
38
- Film.should respond_to(:redis)
39
- film.should respond_to(:redis)
38
+ expect(Film).to respond_to(:redis)
39
+ expect(film).to respond_to(:redis)
40
40
 
41
- Film.redis.should be_a(Redis)
42
- Film.redis.should equal(film.redis)
41
+ expect(Film.redis).to be_a(Redis)
42
+ expect(Film.redis).to equal(film.redis)
43
43
  end
44
44
  end
45
45
 
46
46
  describe ".redis_attrs" do
47
47
  it "adds getters and setters for the attributes defined" do
48
48
  %w(title released_on length created_at rating featured).each do |attr|
49
- film.should respond_to(attr)
50
- film.should respond_to("#{attr}=")
49
+ expect(film).to respond_to(attr)
50
+ expect(film).to respond_to("#{attr}=")
51
51
  end
52
52
  end
53
53
 
54
54
  context "with no paremeters" do
55
+ let(:attrs) { [:title, :released_on, :length, :created_at, :rating, :featured] }
56
+ let(:types) { [:string, :date, :integer, :time, :float, :boolean] }
57
+
55
58
  it "returns a list of all Redis attributes defined for the class" do
56
- Film.redis_attrs.should be_a(Array)
57
- Film.redis_attrs.count.should == 6
58
- Film.redis_attrs.map(&:name).should == [:title, :released_on, :length, :created_at, :rating, :featured]
59
- Film.redis_attrs.map(&:type).should == [:string, :date, :integer, :time, :float, :boolean]
59
+ expect(Film.redis_attrs).to be_a(Array)
60
+ expect(Film.redis_attrs.count).to eq(6)
61
+ expect(Film.redis_attrs.map(&:name)).to eq(attrs)
62
+ expect(Film.redis_attrs.map(&:type)).to eq(types)
60
63
  end
61
64
  end
62
65
  end
@@ -64,119 +67,121 @@ describe Redis::Attrs do
64
67
  describe "getters" do
65
68
  let(:now) { Time.parse("2013-02-22 22:31:12 -0500") }
66
69
 
67
- it "return nil by default" do
68
- film.title.should be_nil
69
- film.released_on.should be_nil
70
- film.length.should be_nil
70
+ it "returns nil by default" do
71
+ expect(film.title).to be_nil
72
+ expect(film.released_on).to be_nil
73
+ expect(film.length).to be_nil
71
74
  end
72
75
 
73
- it "return whatever was last set with the corresponding setter" do
76
+ it "returns whatever was last set with the corresponding setter" do
74
77
  film.title = "Argo"
75
- film.title.should == "Argo"
78
+ expect(film.title).to eq("Argo")
76
79
  end
77
80
 
78
- it "keep the original value type" do
81
+ it "keeps the original value type" do
79
82
  film.released_on = Date.parse("2012-10-12")
80
- film.released_on.should == Date.parse("2012-10-12")
83
+ expect(film.released_on).to eq(Date.parse("2012-10-12"))
81
84
  film.created_at = now
82
- film.created_at.should == now
85
+ expect(film.created_at).to eq(now)
83
86
  film.length = 135
84
- film.length.should == 135
87
+ expect(film.length).to eq(135)
85
88
  film.rating = 8.2
86
- film.rating.should == 8.2
89
+ expect(film.rating).to eq(8.2)
87
90
  film.featured = true
88
- film.featured.should == true
91
+ expect(film.featured).to eq(true)
89
92
  end
90
93
  end
91
94
 
92
95
  describe "setters" do
93
- it "set the corresponding key in Redis" do
96
+ it "sets the corresponding key in Redis" do
94
97
  film.title = "Argo"
95
- redis.get("film:1:title").should == "Argo"
98
+ expect(redis.get("film:1:title")).to eq("Argo")
96
99
 
97
100
  film.rating = 8.1
98
- redis.get("film:1:rating").should == "8.1"
101
+ expect(redis.get("film:1:rating")).to eq("8.1")
99
102
  end
100
103
 
101
- it "unset the key when being assigned nil" do
104
+ it "unsets the key when being assigned nil" do
102
105
  film.rating = 8.1
103
- redis.keys.should include("film:1:rating")
106
+ expect(redis.keys).to include("film:1:rating")
104
107
 
105
108
  film.rating = nil
106
- redis.keys.should_not include("film:1:rating")
107
- redis.get("film:1:rating").should be_nil
109
+ expect(redis.keys).not_to include("film:1:rating")
110
+ expect(redis.get("film:1:rating")).to be_nil
108
111
  end
109
112
  end
110
113
 
111
114
  describe ".register_type" do
115
+ let(:director) { { first_name: "Ben", last_name: "Affleck" } }
116
+
112
117
  it "allows to define support for scalar value types not covered by the library" do
113
118
  Redis::Attrs.register_type(:json, JSONScalar)
114
119
  Film.redis_attrs director: :json
115
- film.director = { first_name: "Ben", last_name: "Affleck" }
116
- redis.keys.should include("film:1:director")
117
- redis.get("film:1:director").should == { first_name: "Ben", last_name: "Affleck" }.to_json
118
- film.director.should == { "first_name" => "Ben", "last_name" => "Affleck" }
120
+ film.director = director
121
+ expect(redis.keys).to include("film:1:director")
122
+ expect(redis.get("film:1:director")).to eq(director.to_json)
123
+ expect(film.director).to eq({ "first_name" => "Ben", "last_name" => "Affleck" })
119
124
  end
120
125
  end
121
126
 
122
127
  describe "collection attributes" do
123
- it "support lists" do
128
+ it "supports lists" do
124
129
  Film.redis_attrs cast: :list
125
- film.cast.should be_empty
130
+ expect(film.cast).to be_empty
126
131
  film.cast = ["Ben Affleck", "Alan Arkin", "John Goodman", "Ben Affleck"]
127
- film.cast.size.should == 4
132
+ expect(film.cast.size).to eq(4)
128
133
  end
129
134
 
130
- it "support hashes" do
135
+ it "supports hashes" do
131
136
  Film.redis_attrs crew: :hash
132
- film.crew.should be_empty
137
+ expect(film.crew).to be_empty
133
138
  film.crew = { costume: "John Doe", makeup: "Jane Doe", camera: "James Doe" }
134
- film.crew.size.should == 3
135
- film.crew.keys.should == %w(costume makeup camera)
139
+ expect(film.crew.size).to eq(3)
140
+ expect(film.crew.keys).to eq(%w(costume makeup camera))
136
141
  end
137
142
 
138
- it "support sets" do
143
+ it "supports sets" do
139
144
  Film.redis_attrs producers: :set
140
- film.producers.should be_empty
145
+ expect(film.producers).to be_empty
141
146
  film.producers = ["Grant Heslov", "Ben Affleck", "George Clooney", "Ben Affleck"]
142
- film.producers.size.should == 3
147
+ expect(film.producers.size).to eq(3)
143
148
  end
144
149
 
145
- it "support sorted sets" do
150
+ it "supports sorted sets" do
146
151
  Film.redis_attrs rankings: :sorted_set
147
- film.rankings.should be_empty
152
+ expect(film.rankings).to be_empty
148
153
  film.rankings = { "oscars" => 3, "golden globe" => 1, "bafta" => 2 }
149
- film.rankings.first.should == "golden globe"
150
- film.rankings.last.should == "oscars"
151
- film.rankings.members.should == ["golden globe", "bafta", "oscars"]
154
+ expect(film.rankings.first).to eq("golden globe")
155
+ expect(film.rankings.last).to eq("oscars")
156
+ expect(film.rankings.members).to eq(["golden globe", "bafta", "oscars"])
152
157
  end
153
158
 
154
- it "support counters" do
159
+ it "supports counters" do
155
160
  Film.redis_attrs awards_count: :counter
156
- film.awards_count.value.should == 0
161
+ expect(film.awards_count.value).to eq(0)
157
162
  film.awards_count.incr
158
- film.awards_count.value.should == 1
163
+ expect(film.awards_count.value).to eq(1)
159
164
  end
160
165
 
161
- it "support locks" do
166
+ it "supports locks" do
162
167
  Film.redis_attrs playing: :lock
163
168
  film.playing.lock { }
164
169
  end
165
170
 
166
- it "support specifying configuration options" do
171
+ it "supports specifying configuration options" do
167
172
  require "active_support/core_ext/numeric/time"
168
- Film.redis_attr :watching, :lock, :expiration => 3.hours
169
- film.watching.options[:expiration].should == 3.hours
173
+ Film.redis_attr :watching, :lock, expiration: 3.hours
174
+ expect(film.watching.options[:expiration]).to eq(3.hours)
170
175
  end
171
176
 
172
177
  it "supports filtering the values inserted into a list or set" do
173
- Film.redis_attr :genres, :set, filter: lambda { |genre| genre.strip.downcase.gsub(/\s+/, ' ') }
178
+ Film.redis_attr :genres, :set, filter: ->(genre) { genre.strip.downcase.gsub(/\s+/, ' ') }
174
179
  film.genres = ["Action ", " drama", "film Noir", "Drama", "Film noir "]
175
- film.genres.members.sort.should == ["action", "drama", "film noir"]
180
+ expect(film.genres.members.sort).to eq(["action", "drama", "film noir"])
176
181
  film.genres << " ACTION " << "Western"
177
- film.genres.should_not include("Western")
178
- film.genres.should include("western")
179
- film.genres.members.sort.should == ["action", "drama", "film noir", "western"]
182
+ expect(film.genres).not_to include("Western")
183
+ expect(film.genres).to include("western")
184
+ expect(film.genres.members.sort).to eq(["action", "drama", "film noir", "western"])
180
185
  end
181
186
  end
182
187
  end
@@ -9,4 +9,9 @@ RSpec.configure do |config|
9
9
  Redis::Attrs.redis.flushdb
10
10
  end
11
11
 
12
+ # Disallow "should" syntax
13
+ config.expect_with :rspec do |c|
14
+ c.syntax = :expect
15
+ end
16
+
12
17
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-attrs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-25 00:00:00.000000000 Z
12
+ date: 2013-08-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
30
46
  - !ruby/object:Gem::Dependency
31
47
  name: redis
32
48
  requirement: !ruby/object:Gem::Requirement
@@ -118,15 +134,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
118
134
  - - ! '>='
119
135
  - !ruby/object:Gem::Version
120
136
  version: '0'
137
+ segments:
138
+ - 0
139
+ hash: -3942124308415567947
121
140
  required_rubygems_version: !ruby/object:Gem::Requirement
122
141
  none: false
123
142
  requirements:
124
143
  - - ! '>='
125
144
  - !ruby/object:Gem::Version
126
145
  version: '0'
146
+ segments:
147
+ - 0
148
+ hash: -3942124308415567947
127
149
  requirements: []
128
150
  rubyforge_project:
129
- rubygems_version: 1.8.24
151
+ rubygems_version: 1.8.23
130
152
  signing_key:
131
153
  specification_version: 3
132
154
  summary: Add persistent object attributes backed by redis