redis-persistence 0.0.1 → 0.0.2
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.
- data/README.markdown +43 -30
- data/examples/benchmark.rb +1 -1
- data/examples/rails-template.rb +15 -23
- data/lib/rails/generators/redis_persistence/initializer/initializer_generator.rb +18 -0
- data/lib/rails/generators/redis_persistence/initializer/templates/initializer.rb.tt +1 -0
- data/lib/rails/generators/redis_persistence/model/model_generator.rb +26 -0
- data/lib/rails/generators/redis_persistence/model/templates/model.rb.tt +9 -0
- data/lib/rails/generators/redis_persistence_generator.rb +42 -0
- data/lib/redis-persistence.rb +1 -0
- data/lib/redis/persistence.rb +202 -29
- data/lib/redis/persistence/railtie.rb +17 -0
- data/lib/redis/persistence/version.rb +1 -1
- data/test/models.rb +16 -0
- data/test/redis_persistence_test.rb +172 -12
- metadata +27 -20
data/README.markdown
CHANGED
@@ -1,50 +1,63 @@
|
|
1
1
|
Redis Persistence
|
2
2
|
=================
|
3
3
|
|
4
|
-
`Redis::Persistence` is a
|
5
|
-
|
4
|
+
`Redis::Persistence` is a lightweight object persistence framework,
|
5
|
+
fully compatible with ActiveModel and based on Redis[http://redis.io],
|
6
|
+
easily used standalone or within Rails applications.
|
6
7
|
|
7
|
-
|
8
|
+
Installation
|
9
|
+
------------
|
8
10
|
|
9
|
-
|
10
|
-
require 'redis/persistence'
|
11
|
+
$ gem install redis-persistence
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
Features:
|
14
|
+
---------
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
* 100% Rails compatibility
|
17
|
+
* 100% ActiveModel compatibility: callbacks, validations, serialization, ...
|
18
|
+
* No crazy `has_many`-type of semantics
|
19
|
+
* Auto-incrementing IDs
|
20
|
+
* Defining default values for properties
|
21
|
+
* Casting properties as built-in or custom classes
|
22
|
+
* Convenient "dot access" to properties (<tt>article.views.today</tt>)
|
23
|
+
* Support for "collections" of embedded objects (eg. article <> comments)
|
24
|
+
* Automatic conversion of UTC-formatted strings to Time objects
|
17
25
|
|
18
|
-
|
19
|
-
|
20
|
-
property :body
|
21
|
-
property :author, :default => '(Unknown)'
|
22
|
-
end
|
26
|
+
Basic example
|
27
|
+
-------------
|
23
28
|
|
24
|
-
|
25
|
-
|
29
|
+
```ruby
|
30
|
+
require 'redis/persistence'
|
26
31
|
|
27
|
-
|
28
|
-
# => #<
|
32
|
+
Redis::Persistence.config.redis = Redis.new
|
33
|
+
# => #<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.4.1)>
|
29
34
|
|
30
|
-
|
31
|
-
|
35
|
+
class Article
|
36
|
+
include Redis::Persistence
|
32
37
|
|
33
|
-
|
34
|
-
|
38
|
+
property :title
|
39
|
+
property :body
|
40
|
+
property :author, :default => '(Unknown)'
|
41
|
+
property :created
|
42
|
+
end
|
35
43
|
|
36
|
-
|
37
|
-
# => "(Unknown)"
|
38
|
-
```
|
44
|
+
Article.create title: 'The Thing', body: 'So, in the beginning...', created: Time.now.utc
|
39
45
|
|
40
|
-
|
41
|
-
|
46
|
+
article = Article.find(1)
|
47
|
+
# => <Article: {"id"=>1, "title"=>"The Thing", ...}>
|
42
48
|
|
49
|
+
article.title
|
50
|
+
# => Hello World!
|
43
51
|
|
44
|
-
|
52
|
+
article.created.class
|
53
|
+
# => Time
|
54
|
+
|
55
|
+
article.title = 'The Cabin'
|
56
|
+
article.save
|
57
|
+
# => <Article: {"id"=>1, "title"=>"The Cabin", ...}>
|
58
|
+
```
|
45
59
|
|
46
|
-
|
47
|
-
rake install
|
60
|
+
See the [`examples/article.rb`](./blob/master/examples/article.rb) for full example.
|
48
61
|
|
49
62
|
-----
|
50
63
|
|
data/examples/benchmark.rb
CHANGED
@@ -40,7 +40,7 @@ end
|
|
40
40
|
puts "Duration: #{elapsed} seconds, rate: #{COUNT.to_f/elapsed} docs/sec",
|
41
41
|
'-'*80
|
42
42
|
|
43
|
-
puts "Finding first 1000 documents with only '
|
43
|
+
puts "Finding first 1000 documents with only 'default' family..."
|
44
44
|
|
45
45
|
elapsed = Benchmark.realtime do
|
46
46
|
Article.find (1..1000).to_a
|
data/examples/rails-template.rb
CHANGED
@@ -54,44 +54,36 @@ puts '-'*80, ''
|
|
54
54
|
run "bundle install"
|
55
55
|
|
56
56
|
puts
|
57
|
-
say_status "Model", "
|
57
|
+
say_status "Model", "Generating the Article resource with Redis::Persistence...", :yellow
|
58
58
|
puts '-'*80, ''; sleep 1
|
59
59
|
|
60
|
-
generate :scaffold, "Article title:string content:text published:
|
60
|
+
generate :scaffold, "Article title:string content:text published:boolean --orm=redis_persistence"
|
61
61
|
route "root :to => 'articles#index'"
|
62
62
|
|
63
63
|
git :add => '.'
|
64
|
-
git :commit => "-m '
|
64
|
+
git :commit => "-m 'Generated the Article resource\n\n(Redis::Persistence-based model, scaffolded controller/views/tests)'"
|
65
65
|
|
66
66
|
puts
|
67
|
-
say_status "
|
67
|
+
say_status "Initializer", "Adding configuration for Redis::Persistence...", :yellow
|
68
68
|
puts '-'*80, ''; sleep 1
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
include Redis::Persistence
|
70
|
+
# initializer 'redis-persistence.rb', <<-CODE
|
71
|
+
# Redis::Persistence.config.redis = Redis.new(:db => 14)
|
72
|
+
# CODE
|
74
73
|
|
75
|
-
|
76
|
-
property :content
|
77
|
-
property :published
|
78
|
-
end
|
79
|
-
CODE
|
74
|
+
generate 'redis_persistence:initializer', "14"
|
80
75
|
|
81
|
-
|
82
|
-
Redis::Persistence
|
83
|
-
CODE
|
84
|
-
|
85
|
-
git :commit => "-a -m 'Added Redis::Persistence into the Article model, added initializer (Redis DB=14)'"
|
76
|
+
git :add => 'config/initializers/redis-persistence.rb'
|
77
|
+
git :commit => "-a -m 'Added initializer for Redis::Persistence\n\n(Find your data with `redis-cli -n 14 keys articles:*`)'"
|
86
78
|
|
87
79
|
puts
|
88
80
|
say_status "Database", "Seeding the database with data...", :yellow
|
89
81
|
puts '-'*80, ''; sleep 0.25
|
90
82
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
83
|
+
remove_file 'db/seeds.rb'
|
84
|
+
file 'db/seeds.rb', <<-CODE
|
85
|
+
Redis::Persistence.config.redis.flushdb
|
86
|
+
|
95
87
|
contents = [
|
96
88
|
'Lorem ipsum dolor sit amet.',
|
97
89
|
'Consectetur adipisicing elit, sed do eiusmod tempor incididunt.',
|
@@ -102,7 +94,7 @@ contents = [
|
|
102
94
|
|
103
95
|
puts "Creating articles..."
|
104
96
|
%w[ One Two Three Four Five ].each_with_index do |title, i|
|
105
|
-
Article.create title: title, content: contents[i], published:
|
97
|
+
Article.create title: title, content: contents[i], published: (rand > 0.5 ? true : false)
|
106
98
|
end
|
107
99
|
CODE
|
108
100
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RedisPersistence
|
2
|
+
module Generators
|
3
|
+
|
4
|
+
class InitializerGenerator < Rails::Generators::Base
|
5
|
+
|
6
|
+
desc "Creates initializer file for redis-persistence in config/initializers."
|
7
|
+
argument :database, :type => :string, :default => '14', :optional => true, :banner => "<REDIS DATABASE NUMBER>"
|
8
|
+
|
9
|
+
source_root File.expand_path("../templates", __FILE__)
|
10
|
+
|
11
|
+
def create_initializer_file
|
12
|
+
template "initializer.rb.tt", "config/initializers/redis-persistence.rb"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Redis::Persistence.config.redis = Redis.new(:db => <%= database %>)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module RedisPersistence
|
2
|
+
module Generators
|
3
|
+
|
4
|
+
class ModelGenerator < Rails::Generators::NamedBase
|
5
|
+
|
6
|
+
desc "Creates a Redis::Persistence-based model."
|
7
|
+
argument :attributes, :type => :array, :default => [], :banner => "property:type property:type ..."
|
8
|
+
|
9
|
+
source_root File.expand_path("../templates", __FILE__)
|
10
|
+
|
11
|
+
check_class_collision
|
12
|
+
|
13
|
+
def create_model_file
|
14
|
+
template "model.rb.tt", File.join("app/models", "#{file_name}.rb")
|
15
|
+
end
|
16
|
+
|
17
|
+
hook_for :test_framework
|
18
|
+
|
19
|
+
def module_namespacing(&block)
|
20
|
+
yield if block
|
21
|
+
end unless methods.include?(:module_namespacing)
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rails/generators/named_base'
|
2
|
+
require 'rails/generators/active_model'
|
3
|
+
|
4
|
+
module RedisPersistence
|
5
|
+
module Generators
|
6
|
+
|
7
|
+
class ActiveModel < ::Rails::Generators::ActiveModel
|
8
|
+
def self.all(klass)
|
9
|
+
"#{klass}.all"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.find(klass, params=nil)
|
13
|
+
"#{klass}.find(#{params})"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.build(klass, params=nil)
|
17
|
+
if params
|
18
|
+
"#{klass}.new(#{params})"
|
19
|
+
else
|
20
|
+
"#{klass}.new"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def save
|
25
|
+
"#{name}.save"
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_attributes(params=nil)
|
29
|
+
"#{name}.update_attributes(#{params})"
|
30
|
+
end
|
31
|
+
|
32
|
+
def errors
|
33
|
+
"#{name}.errors"
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy
|
37
|
+
"#{name}.destroy"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'redis/persistence'
|
data/lib/redis/persistence.rb
CHANGED
@@ -5,11 +5,61 @@ require 'active_model'
|
|
5
5
|
require 'active_support/concern'
|
6
6
|
require 'active_support/configurable'
|
7
7
|
|
8
|
+
require File.expand_path('../persistence/railtie', __FILE__) if defined?(Rails)
|
9
|
+
|
8
10
|
class Redis
|
11
|
+
|
12
|
+
# <b>Redis::Persistence</b> is a lightweight object persistence framework,
|
13
|
+
# fully compatible with ActiveModel and based on Redis[http://redis.io].
|
14
|
+
#
|
15
|
+
# Features:
|
16
|
+
#
|
17
|
+
# * 100% Rails compatibility
|
18
|
+
# * 100% ActiveModel compatibility: callbacks, validations, serialization, ...
|
19
|
+
# * No crazy +has_many+-type of semantics
|
20
|
+
# * Auto-incrementing IDs
|
21
|
+
# * Defining default values for properties
|
22
|
+
# * Casting properties as built-in or custom classes
|
23
|
+
# * Convenient "dot access" to properties (<tt>article.views.today</tt>)
|
24
|
+
# * Support for "collections" of embedded objects (eg. article <> comments)
|
25
|
+
# * Automatic conversion of UTC-formatted strings to Time objects
|
26
|
+
#
|
27
|
+
# Basic example:
|
28
|
+
#
|
29
|
+
# class Article
|
30
|
+
# include Redis::Persistence
|
31
|
+
#
|
32
|
+
# property :title
|
33
|
+
# property :body
|
34
|
+
# property :author, :default => '(Unknown)'
|
35
|
+
# property :created
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# Article.create title: 'Hello World!', body: 'So, in the beginning...', created: Time.now.utc
|
39
|
+
# article = Article.find(1)
|
40
|
+
# # => <Article: {"id"=>1, "title"=>"Hello World!", ...}>
|
41
|
+
# article.title
|
42
|
+
# # => Hello World!
|
43
|
+
# article.created.class
|
44
|
+
# # => Time
|
45
|
+
#
|
46
|
+
# See the <tt>examples/article.rb</tt> for full example.
|
47
|
+
#
|
9
48
|
module Persistence
|
10
|
-
include ActiveSupport::Configurable
|
11
49
|
extend ActiveSupport::Concern
|
12
50
|
|
51
|
+
class RedisNotAvailable < StandardError; end
|
52
|
+
|
53
|
+
DEFAULT_FAMILY = 'default'
|
54
|
+
|
55
|
+
def self.config
|
56
|
+
@__config ||= Hashr.new
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.configure
|
60
|
+
yield config
|
61
|
+
end
|
62
|
+
|
13
63
|
included do
|
14
64
|
include ActiveModelIntegration
|
15
65
|
self.include_root_in_json = false
|
@@ -21,7 +71,6 @@ class Redis
|
|
21
71
|
def __redis
|
22
72
|
self.class.__redis
|
23
73
|
end
|
24
|
-
|
25
74
|
end
|
26
75
|
|
27
76
|
module ActiveModelIntegration
|
@@ -42,88 +91,175 @@ class Redis
|
|
42
91
|
|
43
92
|
module ClassMethods
|
44
93
|
|
94
|
+
# Create new record in database:
|
95
|
+
#
|
96
|
+
# Article.create title: 'Lorem Ipsum'
|
97
|
+
#
|
45
98
|
def create(attributes={})
|
46
99
|
new(attributes).save
|
47
100
|
end
|
48
101
|
|
102
|
+
# Define property in the "default" family:
|
103
|
+
#
|
104
|
+
# property :title
|
105
|
+
# property :author, class: Author
|
106
|
+
# property :comments, default: []
|
107
|
+
# property :comments, default: [], class: [Comment]
|
108
|
+
#
|
109
|
+
# Specify a custom "family" for this property:
|
110
|
+
#
|
111
|
+
# property :views, family: 'counters'
|
112
|
+
#
|
113
|
+
# Only the "default" family is loaded... by default,
|
114
|
+
# for performance and limiting used memory.
|
115
|
+
#
|
116
|
+
# See more examples in the <tt>test/models.rb</tt> file.
|
117
|
+
#
|
49
118
|
def property(name, options = {})
|
50
|
-
|
119
|
+
# Getter method
|
120
|
+
#
|
121
|
+
attr_reader name.to_sym
|
122
|
+
|
123
|
+
# Setter method
|
124
|
+
#
|
125
|
+
define_method("#{name}=") do |value|
|
126
|
+
# When changing property, update also loaded family:
|
127
|
+
if instance_variable_get(:"@#{name}") != value && self.class.property_defaults[name.to_sym] != value
|
128
|
+
self.__loaded_families |= self.class.property_families.invert.map do |key, value|
|
129
|
+
value.to_s if key.map(&:to_s).include?(name.to_s)
|
130
|
+
end.compact
|
131
|
+
end
|
132
|
+
# Store the value in instance variable:
|
133
|
+
instance_variable_set(:"@#{name}", value)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Save the property in properties array:
|
51
137
|
properties << name.to_s unless properties.include?(name.to_s)
|
52
138
|
|
139
|
+
# Save property default value (when relevant):
|
53
140
|
property_defaults[name.to_sym] = options[:default] if options[:default]
|
141
|
+
|
142
|
+
# Save property casting (when relevant):
|
54
143
|
property_types[name.to_sym] = options[:class] if options[:class]
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
144
|
+
|
145
|
+
# Save the property in corresponding family:
|
146
|
+
if options[:family]; (property_families[options[:family].to_sym] ||= []) << name.to_s
|
147
|
+
else; (property_families[DEFAULT_FAMILY.to_sym] ||= []) << name.to_s
|
59
148
|
end
|
149
|
+
|
60
150
|
self
|
61
151
|
end
|
62
152
|
|
153
|
+
# Returns an Array with all properties
|
154
|
+
#
|
63
155
|
def properties
|
64
156
|
@properties ||= ['id']
|
65
157
|
end
|
66
158
|
|
159
|
+
# Returns a Hash with property default values
|
160
|
+
#
|
67
161
|
def property_defaults
|
68
162
|
@property_defaults ||= {}
|
69
163
|
end
|
70
164
|
|
165
|
+
# Returns a Hash with property casting (classes)
|
166
|
+
#
|
71
167
|
def property_types
|
72
168
|
@property_types ||= {}
|
73
169
|
end
|
74
170
|
|
171
|
+
# Returns a Hash mapping families to properties
|
172
|
+
#
|
75
173
|
def property_families
|
76
|
-
@property_families ||= {
|
174
|
+
@property_families ||= { DEFAULT_FAMILY.to_sym => ['id'] }
|
77
175
|
end
|
78
176
|
|
177
|
+
# Find one or multiple records
|
178
|
+
#
|
179
|
+
# Article.find 1
|
180
|
+
# Article.find [1, 2, 3]
|
181
|
+
#
|
182
|
+
# Specify a family (other then "default"):
|
183
|
+
#
|
184
|
+
# Article.find 1, families: 'counters'
|
185
|
+
# Article.find 1, families: ['counters', 'meta']
|
186
|
+
#
|
79
187
|
def find(args, options={})
|
80
188
|
args.is_a?(Array) ? __find_many(args, options) : __find_one(args, options)
|
81
189
|
end
|
82
190
|
|
191
|
+
# Yield each record in the database, loading them in batches
|
192
|
+
# specified as +batch_size+:
|
193
|
+
#
|
194
|
+
# Article.find_each do |article|
|
195
|
+
# article.title += ' (touched)' and article.save
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# This method is conveninent for batch manipulations of your entire database.
|
199
|
+
#
|
200
|
+
def find_each(options={}, &block)
|
201
|
+
batch_size = options.delete(:batch_size) || 1000
|
202
|
+
__all_ids.each_slice batch_size do |batch|
|
203
|
+
__find_many(batch, options).each { |document| yield document }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
83
207
|
def __find_one(id, options={})
|
84
|
-
families = ['
|
208
|
+
families = options[:families] == 'all' ? property_families.keys : [DEFAULT_FAMILY.to_s] | Array(options[:families])
|
85
209
|
data = __redis.hmget("#{self.model_name.plural}:#{id}", *families)
|
86
210
|
|
87
211
|
unless data.compact.empty?
|
88
|
-
attributes = data.inject({}) { |hash, item| hash.update( MultiJson.decode(item) ); hash }
|
89
|
-
self.new attributes
|
212
|
+
attributes = data.compact.inject({}) { |hash, item| hash.update( MultiJson.decode(item, :symbolize_keys => true) ); hash }
|
213
|
+
instance = self.new attributes
|
214
|
+
instance.__loaded_families = families
|
215
|
+
instance
|
90
216
|
end
|
91
217
|
end
|
92
218
|
|
93
|
-
def __find_all(options={})
|
94
|
-
__find_many __all_ids
|
95
|
-
end
|
96
|
-
alias :all :__find_all
|
97
|
-
|
98
219
|
def __find_many(ids, options={})
|
99
220
|
ids.map { |id| __find_one(id, options) }.compact
|
100
221
|
end
|
101
222
|
|
102
|
-
def
|
103
|
-
|
104
|
-
__all_ids.each_slice options[:batch_size] do |batch|
|
105
|
-
__find_many(batch).each { |document| yield document }
|
106
|
-
end
|
223
|
+
def __find_all(options={})
|
224
|
+
__find_many __all_ids
|
107
225
|
end
|
108
226
|
|
227
|
+
# Find all records in the database:
|
228
|
+
#
|
229
|
+
# Article.all
|
230
|
+
#
|
231
|
+
alias :all :__find_all
|
232
|
+
|
109
233
|
def __next_id
|
110
234
|
__redis.incr("#{self.model_name.plural}_ids")
|
111
235
|
end
|
112
236
|
|
113
237
|
def __all_ids
|
114
|
-
__redis.keys("#{self.model_name.plural}:*").map { |id| id[/:(
|
238
|
+
__redis.keys("#{self.model_name.plural}:*").map { |id| id[/:(.+)$/, 1] }.sort
|
115
239
|
end
|
116
240
|
|
117
241
|
end
|
118
242
|
|
119
243
|
module InstanceMethods
|
120
244
|
attr_accessor :id
|
245
|
+
attr_writer :__loaded_families
|
121
246
|
|
122
247
|
def initialize(attributes={})
|
123
|
-
|
248
|
+
# Store "loaded_families" based on passed attributes, for using when saving:
|
249
|
+
self.class.property_families.each do |name, properties|
|
250
|
+
self.__loaded_families |= [name.to_s] if ( properties.map(&:to_s) & attributes.keys.map(&:to_s) ).size > 0
|
251
|
+
end
|
252
|
+
|
253
|
+
# Make copy of objects in the property defaults hash (so default values are left intact):
|
254
|
+
property_defaults = self.class.property_defaults.inject({}) do |sum, item|
|
255
|
+
key, value = item
|
256
|
+
sum[key] = value.class.respond_to?(:new) ? value.clone : value
|
257
|
+
sum
|
258
|
+
end
|
259
|
+
|
260
|
+
__update_attributes property_defaults.merge(attributes)
|
124
261
|
self
|
125
|
-
end
|
126
|
-
alias :attributes= :initialize
|
262
|
+
end; alias :attributes= :initialize
|
127
263
|
|
128
264
|
def update_attributes(attributes={})
|
129
265
|
__update_attributes attributes
|
@@ -131,23 +267,44 @@ class Redis
|
|
131
267
|
self
|
132
268
|
end
|
133
269
|
|
270
|
+
# Returns a Hash of attributes for serialization, etc
|
271
|
+
#
|
134
272
|
def attributes
|
135
273
|
self.class.
|
136
274
|
properties.
|
137
275
|
inject({}) {|attributes, key| attributes[key] = send(key); attributes}
|
138
276
|
end
|
139
277
|
|
140
|
-
|
278
|
+
# Saves the record in the database, performing callbacks:
|
279
|
+
#
|
280
|
+
# Article.new(title: 'Test').save
|
281
|
+
#
|
282
|
+
# Optionally accepts which families to save:
|
283
|
+
#
|
284
|
+
# article = Article.find(1, families: 'counters')
|
285
|
+
# article.views += 1
|
286
|
+
# article.save(families: ['counters', 'meta'])
|
287
|
+
#
|
288
|
+
# You can also save all families:
|
289
|
+
#
|
290
|
+
# Article.find(1, families: 'all').update_attributes(title: 'Changed').save(families: 'all')
|
291
|
+
#
|
292
|
+
# Be careful not to overwrite properties with default values.
|
293
|
+
#
|
294
|
+
def save(options={})
|
141
295
|
run_callbacks :save do
|
142
296
|
self.id ||= self.class.__next_id
|
143
|
-
|
144
|
-
|
297
|
+
families = options[:families] == 'all' ? self.class.property_families.keys : self.__loaded_families
|
298
|
+
params = families.map do |family|
|
299
|
+
[family.to_s, self.to_json(:only => self.class.property_families[family.to_sym])]
|
145
300
|
end.flatten
|
146
301
|
__redis.hmset "#{self.class.model_name.plural}:#{self.id}", *params
|
147
302
|
end
|
148
303
|
self
|
149
304
|
end
|
150
305
|
|
306
|
+
# Removes the record from the database, performing callbacks.
|
307
|
+
#
|
151
308
|
def destroy
|
152
309
|
run_callbacks :destroy do
|
153
310
|
__redis.del "#{self.class.model_name.plural}:#{self.id}"
|
@@ -155,6 +312,8 @@ class Redis
|
|
155
312
|
self.freeze
|
156
313
|
end
|
157
314
|
|
315
|
+
# Returns whether record is saved into database
|
316
|
+
#
|
158
317
|
def persisted?
|
159
318
|
__redis.exists "#{self.class.model_name.plural}:#{self.id}"
|
160
319
|
end
|
@@ -163,25 +322,39 @@ class Redis
|
|
163
322
|
"#<#{self.class}: #{attributes}>"
|
164
323
|
end
|
165
324
|
|
325
|
+
# Updates record properties, taking care of casting to specified classes
|
326
|
+
# (single values or collections), augmenting hashes so you can access them with dot notation,
|
327
|
+
# and automatically converting properly formatted time values to Time classes.
|
328
|
+
#
|
166
329
|
def __update_attributes(attributes)
|
167
330
|
attributes.each do |name, value|
|
168
331
|
case
|
332
|
+
# Should we cast the value ...
|
169
333
|
when klass = self.class.property_types[name.to_sym]
|
334
|
+
# ... as an Array ...
|
170
335
|
if klass.is_a?(Array) && value.is_a?(Array)
|
171
336
|
send "#{name}=", value.map { |v| klass.first.new(v) }
|
337
|
+
# ... or object?
|
172
338
|
else
|
173
339
|
send "#{name}=", klass.new(value)
|
174
340
|
end
|
341
|
+
# Should we return augmented Hash?
|
175
342
|
when value.is_a?(Hash)
|
176
343
|
send "#{name}=", Hashr.new(value)
|
177
344
|
else
|
178
|
-
#
|
345
|
+
# Strings formatted as <http://en.wikipedia.org/wiki/ISO8601> are automatically converted to Time
|
179
346
|
value = Time.parse(value) if value.is_a?(String) && value =~ /^\d{4}[\/\-]\d{2}[\/\-]\d{2}T\d{2}\:\d{2}\:\d{2}Z$/
|
180
347
|
send "#{name}=", value
|
181
348
|
end
|
182
349
|
end
|
183
350
|
end
|
184
351
|
|
352
|
+
# Returns which families were loaded in the record lifecycle
|
353
|
+
#
|
354
|
+
def __loaded_families
|
355
|
+
@__loaded_families ||= [DEFAULT_FAMILY.to_s]
|
356
|
+
end
|
357
|
+
|
185
358
|
end
|
186
359
|
|
187
360
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module RedisPersistence
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
|
4
|
+
initializer "warn when configuration is missing" do
|
5
|
+
config.after_initialize do
|
6
|
+
unless ::Redis::Persistence.config.redis
|
7
|
+
puts "\n[ERROR!] Redis::Persistence is not configured!", '='*80,
|
8
|
+
"Please point `Redis::Persistence.config.redis` to a Redis instance, ",
|
9
|
+
"if you actually intend to save some data :)\n\n",
|
10
|
+
"You can create an initializer with:\n\n",
|
11
|
+
" $ rails generate redis_persistence:initializer\n\n", '-'*80, "\n"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/test/models.rb
CHANGED
@@ -103,4 +103,20 @@ end
|
|
103
103
|
class ModelWithCastingInFamily
|
104
104
|
include Redis::Persistence
|
105
105
|
property :pieces, :class => [Piece], :default => [], :family => 'meta'
|
106
|
+
property :parts, :class => [Piece], :default => [], :family => 'meta'
|
107
|
+
end
|
108
|
+
|
109
|
+
class ModelWithDefaultArray
|
110
|
+
include Redis::Persistence
|
111
|
+
|
112
|
+
property :accounts, :default => []
|
113
|
+
property :options, :default => { :switches => []}
|
114
|
+
property :deep, :default => { :one => { :two => { :three => [] } } }
|
115
|
+
end
|
116
|
+
|
117
|
+
class ModelWithDefaultsInFamilies
|
118
|
+
include Redis::Persistence
|
119
|
+
|
120
|
+
property :name
|
121
|
+
property :tags, :default => [], :family => 'tags'
|
106
122
|
end
|
@@ -8,14 +8,40 @@ class RedisPersistenceTest < ActiveSupport::TestCase
|
|
8
8
|
Redis::Persistence.config.redis
|
9
9
|
end
|
10
10
|
|
11
|
-
context "
|
12
|
-
|
11
|
+
context "Configuration" do
|
12
|
+
|
13
|
+
should "be configurable" do
|
14
|
+
assert_respond_to Redis::Persistence, :config
|
15
|
+
assert_nothing_raised do
|
16
|
+
Redis::Persistence.config.foo = 'bar'
|
17
|
+
assert_equal 'bar', Redis::Persistence.config.foo
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
should "be configurable with a block" do
|
22
|
+
assert_respond_to Redis::Persistence, :configure
|
23
|
+
assert_nothing_raised do
|
24
|
+
Redis::Persistence.configure { |config| config.foo = 'bar' }
|
25
|
+
assert_equal 'bar', Redis::Persistence.config.foo
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
context "Redis" do
|
32
|
+
teardown { Redis::Persistence.config.redis = Redis.new db: ENV['REDIS_PERSISTENCE_TEST_DATABASE'] || 14 }
|
33
|
+
|
13
34
|
should "be set" do
|
14
35
|
assert_nothing_raised { in_redis.info }
|
15
36
|
end
|
16
|
-
|
37
|
+
|
38
|
+
should_eventually "raise error when trying to access it when not configured" do
|
39
|
+
Redis::Persistence.config.redis = nil
|
40
|
+
assert_raise(Redis::Persistence::RedisNotAvailable) { Redis::Persistence.config.redis }
|
41
|
+
end
|
42
|
+
|
17
43
|
end
|
18
|
-
|
44
|
+
|
19
45
|
context "Defining properties" do
|
20
46
|
|
21
47
|
should "define accessors from attributes" do
|
@@ -108,12 +134,12 @@ class RedisPersistenceTest < ActiveSupport::TestCase
|
|
108
134
|
|
109
135
|
context "Defining properties in families" do
|
110
136
|
|
111
|
-
should "store properties in the '
|
137
|
+
should "store properties in the 'default' family by default" do
|
112
138
|
m = ModelWithFamily.new name: 'One'
|
113
139
|
m.save
|
114
140
|
|
115
141
|
assert in_redis.exists('model_with_families:1'), in_redis.keys.to_s
|
116
|
-
assert in_redis.hkeys('model_with_families:1').include?('
|
142
|
+
assert in_redis.hkeys('model_with_families:1').include?('default')
|
117
143
|
end
|
118
144
|
|
119
145
|
should "store properties in the correct family" do
|
@@ -122,7 +148,7 @@ class RedisPersistenceTest < ActiveSupport::TestCase
|
|
122
148
|
|
123
149
|
assert_equal 1, m.id
|
124
150
|
assert in_redis.exists('model_with_families:1'), in_redis.keys.to_s
|
125
|
-
assert in_redis.hkeys('model_with_families:1').include?('
|
151
|
+
assert in_redis.hkeys('model_with_families:1').include?('default'), in_redis.hkeys('model_with_families:1').to_s
|
126
152
|
assert in_redis.hkeys('model_with_families:1').include?('counters'), in_redis.hkeys('model_with_families:1').to_s
|
127
153
|
|
128
154
|
m = ModelWithFamily.find(1)
|
@@ -144,13 +170,43 @@ class RedisPersistenceTest < ActiveSupport::TestCase
|
|
144
170
|
|
145
171
|
m = ModelWithCastingInFamily.find(1)
|
146
172
|
assert_equal [], m.pieces
|
173
|
+
assert_equal [], m.parts
|
147
174
|
assert_nil m.pieces.first
|
148
175
|
|
149
176
|
m = ModelWithCastingInFamily.find(1, :families => 'meta')
|
177
|
+
assert_equal [], m.parts
|
150
178
|
assert_not_nil m.pieces.first
|
151
179
|
assert_equal 42, m.pieces.first.level
|
152
180
|
end
|
153
181
|
|
182
|
+
should "store loaded families on initialization" do
|
183
|
+
m = ModelWithCastingInFamily.new pieces: [ { name: 'One', level: 42 } ]
|
184
|
+
assert_equal ['default', 'meta'], m.__loaded_families
|
185
|
+
|
186
|
+
m = ModelWithCastingInFamily.new
|
187
|
+
assert_equal ['default'], m.__loaded_families
|
188
|
+
end
|
189
|
+
|
190
|
+
should "store loaded families on find" do
|
191
|
+
ModelWithCastingInFamily.create pieces: [ { name: 'One', level: 42 } ]
|
192
|
+
m = ModelWithCastingInFamily.find(1)
|
193
|
+
assert_equal ['default'], m.__loaded_families
|
194
|
+
|
195
|
+
ModelWithCastingInFamily.create pieces: [ { name: 'One', level: 42 } ]
|
196
|
+
m = ModelWithCastingInFamily.find(1, families: 'meta')
|
197
|
+
assert_equal ['default', 'meta'], m.__loaded_families
|
198
|
+
end
|
199
|
+
|
200
|
+
should "update loaded families on property assignment" do
|
201
|
+
m = ModelWithFamily.new name: 'Test'
|
202
|
+
assert_equal ['default'], m.__loaded_families
|
203
|
+
assert_equal 'Test', m.name
|
204
|
+
|
205
|
+
m.views = 100
|
206
|
+
assert_equal ['default', 'counters'], m.__loaded_families
|
207
|
+
assert_equal 100, m.views
|
208
|
+
end
|
209
|
+
|
154
210
|
end
|
155
211
|
|
156
212
|
context "Class" do
|
@@ -205,6 +261,20 @@ class RedisPersistenceTest < ActiveSupport::TestCase
|
|
205
261
|
assert_match /touched/, PersistentArticle.find(1).title
|
206
262
|
end
|
207
263
|
|
264
|
+
should "load all families" do
|
265
|
+
ModelWithFamily.create name: 'One', views: 10, lang: 'en'
|
266
|
+
|
267
|
+
m = ModelWithFamily.find(1)
|
268
|
+
assert_equal 1, m.__loaded_families.size
|
269
|
+
assert_nil m.views
|
270
|
+
|
271
|
+
m = ModelWithFamily.find(1, families: 'all')
|
272
|
+
assert_equal 3, m.__loaded_families.size
|
273
|
+
assert_equal 'One', m.name
|
274
|
+
assert_equal 10, m.views
|
275
|
+
assert_equal 'en', m.lang
|
276
|
+
end
|
277
|
+
|
208
278
|
end
|
209
279
|
|
210
280
|
context "Instance" do
|
@@ -224,24 +294,57 @@ class RedisPersistenceTest < ActiveSupport::TestCase
|
|
224
294
|
end
|
225
295
|
|
226
296
|
should "be saved and found in Redis" do
|
227
|
-
article = PersistentArticle.new
|
297
|
+
article = PersistentArticle.new title: 'One'
|
228
298
|
assert article.save
|
229
299
|
assert in_redis.exists("persistent_articles:1")
|
230
300
|
|
231
|
-
|
301
|
+
assert_equal 1, PersistentArticle.all.size
|
302
|
+
assert_not_nil PersistentArticle.find(1)
|
232
303
|
assert in_redis.keys.size > 0, 'Key not saved into Redis?'
|
233
304
|
assert_equal 'One', PersistentArticle.find(1).title
|
234
305
|
end
|
235
306
|
|
236
307
|
should "be deleted from Redis" do
|
237
|
-
article = PersistentArticle.new
|
308
|
+
article = PersistentArticle.new title: 'One'
|
238
309
|
assert article.save
|
310
|
+
keys_count = in_redis.keys.size
|
311
|
+
|
239
312
|
assert_not_nil PersistentArticle.find(1)
|
240
|
-
assert
|
313
|
+
assert keys_count > 0, 'Key not saved into Redis?'
|
241
314
|
|
242
315
|
article.destroy
|
243
316
|
assert_nil PersistentArticle.find(1)
|
244
|
-
|
317
|
+
assert in_redis.keys.size < keys_count, 'Key not removed from Redis?'
|
318
|
+
end
|
319
|
+
|
320
|
+
should "be saved, found and deleted with an arbitrary ID" do
|
321
|
+
article = PersistentArticle.new id: 'abc123', title: 'Special'
|
322
|
+
assert article.save
|
323
|
+
keys_count = in_redis.keys.size
|
324
|
+
|
325
|
+
assert_equal 1, PersistentArticle.all.size
|
326
|
+
assert_not_nil PersistentArticle.find('abc123')
|
327
|
+
|
328
|
+
assert keys_count > 0, 'Key not saved into Redis?'
|
329
|
+
assert_equal 'Special', PersistentArticle.find('abc123').title
|
330
|
+
|
331
|
+
assert article.destroy
|
332
|
+
assert_equal 0, PersistentArticle.all.size
|
333
|
+
assert_nil PersistentArticle.find('abc123')
|
334
|
+
assert in_redis.keys.size < keys_count, 'Key not removed from Redis?'
|
335
|
+
end
|
336
|
+
|
337
|
+
should "save all families" do
|
338
|
+
m = ModelWithFamily.new name: 'Test'
|
339
|
+
assert m.save
|
340
|
+
assert_equal 1, in_redis.hkeys("model_with_families:1").size
|
341
|
+
|
342
|
+
m = ModelWithFamily.new name: 'Test'
|
343
|
+
assert m.save(families: 'all')
|
344
|
+
assert in_redis.keys.size > 0, 'Key not saved into Redis?'
|
345
|
+
|
346
|
+
# ["default", "counters", "meta"]
|
347
|
+
assert_equal 3, in_redis.hkeys("model_with_families:2").size
|
245
348
|
end
|
246
349
|
|
247
350
|
should "update attributes" do
|
@@ -288,6 +391,63 @@ class RedisPersistenceTest < ActiveSupport::TestCase
|
|
288
391
|
assert_equal 1, m.errors.to_a.size
|
289
392
|
end
|
290
393
|
|
394
|
+
should "not change default value when assigning property" do
|
395
|
+
m = ModelWithDefaultArray.new
|
396
|
+
|
397
|
+
m.accounts << "account_1"
|
398
|
+
m.options[:switches] << "switch_1"
|
399
|
+
m.deep[:one][:two][:three] << 'foo'
|
400
|
+
m.deep.one.two.three << 'four'
|
401
|
+
|
402
|
+
assert_equal [], ModelWithDefaultArray.new.accounts
|
403
|
+
assert_equal [], ModelWithDefaultArray.new.options[:switches]
|
404
|
+
assert_equal [], ModelWithDefaultArray.new.deep[:one][:two][:three]
|
405
|
+
assert_equal [], ModelWithDefaultArray.new.deep.one.two.three
|
406
|
+
|
407
|
+
assert_equal [], ModelWithDefaultArray.property_defaults[:accounts]
|
408
|
+
assert_equal [], ModelWithDefaultArray.property_defaults[:options][:switches]
|
409
|
+
assert_equal [], ModelWithDefaultArray.property_defaults[:deep][:one][:two][:three]
|
410
|
+
end
|
411
|
+
|
412
|
+
should "not overwrite properties in not-loaded family with defaults" do
|
413
|
+
m = ModelWithDefaultsInFamilies.new :name => 'One'
|
414
|
+
m.save
|
415
|
+
|
416
|
+
# Return defaults
|
417
|
+
m = ModelWithDefaultsInFamilies.find(1)
|
418
|
+
assert_equal [], m.tags
|
419
|
+
|
420
|
+
# Return defaults
|
421
|
+
m = ModelWithDefaultsInFamilies.find(1, families: 'tags')
|
422
|
+
assert_equal [], m.tags
|
423
|
+
|
424
|
+
# Add tag
|
425
|
+
m = ModelWithDefaultsInFamilies.find(1, families: 'tags')
|
426
|
+
m.tags << 'foo'
|
427
|
+
m.save
|
428
|
+
|
429
|
+
# Return data
|
430
|
+
m = ModelWithDefaultsInFamilies.find(1, :families => 'tags')
|
431
|
+
assert_equal ['foo'], m.tags
|
432
|
+
|
433
|
+
# Return defaults
|
434
|
+
m = ModelWithDefaultsInFamilies.find(1)
|
435
|
+
assert_equal [], m.tags
|
436
|
+
|
437
|
+
# Change another property
|
438
|
+
m = ModelWithDefaultsInFamilies.find(1)
|
439
|
+
m.name = 'Two'
|
440
|
+
m.save
|
441
|
+
|
442
|
+
# Return defaults
|
443
|
+
m = ModelWithDefaultsInFamilies.find(1)
|
444
|
+
assert_equal [], m.tags
|
445
|
+
|
446
|
+
# Return data
|
447
|
+
m = ModelWithDefaultsInFamilies.find(1, :families => 'tags')
|
448
|
+
assert_equal ['foo'], m.tags
|
449
|
+
end
|
450
|
+
|
291
451
|
end
|
292
452
|
|
293
453
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-persistence
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2011-11-
|
13
|
+
date: 2011-11-20 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activemodel
|
17
|
-
requirement: &
|
17
|
+
requirement: &70111331651120 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ~>
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: '3.0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70111331651120
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: multi_json
|
28
|
-
requirement: &
|
28
|
+
requirement: &70111331650560 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ~>
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: '1.0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70111331650560
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: redis
|
39
|
-
requirement: &
|
39
|
+
requirement: &70111331650040 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ~>
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: 2.2.2
|
45
45
|
type: :runtime
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *70111331650040
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: hashr
|
50
|
-
requirement: &
|
50
|
+
requirement: &70111331649460 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ~>
|
@@ -55,10 +55,10 @@ dependencies:
|
|
55
55
|
version: 0.0.16
|
56
56
|
type: :runtime
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *70111331649460
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: bundler
|
61
|
-
requirement: &
|
61
|
+
requirement: &70111331648800 !ruby/object:Gem::Requirement
|
62
62
|
none: false
|
63
63
|
requirements:
|
64
64
|
- - ~>
|
@@ -66,10 +66,10 @@ dependencies:
|
|
66
66
|
version: '1.0'
|
67
67
|
type: :development
|
68
68
|
prerelease: false
|
69
|
-
version_requirements: *
|
69
|
+
version_requirements: *70111331648800
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: yajl-ruby
|
72
|
-
requirement: &
|
72
|
+
requirement: &70111331647980 !ruby/object:Gem::Requirement
|
73
73
|
none: false
|
74
74
|
requirements:
|
75
75
|
- - ~>
|
@@ -77,10 +77,10 @@ dependencies:
|
|
77
77
|
version: 0.8.0
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
|
-
version_requirements: *
|
80
|
+
version_requirements: *70111331647980
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
82
|
name: shoulda
|
83
|
-
requirement: &
|
83
|
+
requirement: &70111331647400 !ruby/object:Gem::Requirement
|
84
84
|
none: false
|
85
85
|
requirements:
|
86
86
|
- - ! '>='
|
@@ -88,10 +88,10 @@ dependencies:
|
|
88
88
|
version: '0'
|
89
89
|
type: :development
|
90
90
|
prerelease: false
|
91
|
-
version_requirements: *
|
91
|
+
version_requirements: *70111331647400
|
92
92
|
- !ruby/object:Gem::Dependency
|
93
93
|
name: mocha
|
94
|
-
requirement: &
|
94
|
+
requirement: &70111331646780 !ruby/object:Gem::Requirement
|
95
95
|
none: false
|
96
96
|
requirements:
|
97
97
|
- - ! '>='
|
@@ -99,10 +99,10 @@ dependencies:
|
|
99
99
|
version: '0'
|
100
100
|
type: :development
|
101
101
|
prerelease: false
|
102
|
-
version_requirements: *
|
102
|
+
version_requirements: *70111331646780
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
104
|
name: turn
|
105
|
-
requirement: &
|
105
|
+
requirement: &70111331645860 !ruby/object:Gem::Requirement
|
106
106
|
none: false
|
107
107
|
requirements:
|
108
108
|
- - ! '>='
|
@@ -110,7 +110,7 @@ dependencies:
|
|
110
110
|
version: '0'
|
111
111
|
type: :development
|
112
112
|
prerelease: false
|
113
|
-
version_requirements: *
|
113
|
+
version_requirements: *70111331645860
|
114
114
|
description: Simple ActiveModel-compatible persistence layer in Redis
|
115
115
|
email:
|
116
116
|
- karmi@karmi.cz
|
@@ -126,7 +126,14 @@ files:
|
|
126
126
|
- examples/article.rb
|
127
127
|
- examples/benchmark.rb
|
128
128
|
- examples/rails-template.rb
|
129
|
+
- lib/rails/generators/redis_persistence/initializer/initializer_generator.rb
|
130
|
+
- lib/rails/generators/redis_persistence/initializer/templates/initializer.rb.tt
|
131
|
+
- lib/rails/generators/redis_persistence/model/model_generator.rb
|
132
|
+
- lib/rails/generators/redis_persistence/model/templates/model.rb.tt
|
133
|
+
- lib/rails/generators/redis_persistence_generator.rb
|
134
|
+
- lib/redis-persistence.rb
|
129
135
|
- lib/redis/persistence.rb
|
136
|
+
- lib/redis/persistence/railtie.rb
|
130
137
|
- lib/redis/persistence/version.rb
|
131
138
|
- redis-persistence.gemspec
|
132
139
|
- test/_helper.rb
|