redpear 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/redpear.rb +23 -0
- data/lib/redpear/column.rb +53 -0
- data/lib/redpear/concern.rb +10 -0
- data/lib/redpear/connection.rb +17 -0
- data/lib/redpear/core_ext/stringify_keys.rb +17 -0
- data/lib/redpear/expiration.rb +27 -0
- data/lib/redpear/finders.rb +51 -0
- data/lib/redpear/index.rb +24 -0
- data/lib/redpear/machinist.rb +50 -0
- data/lib/redpear/model.rb +109 -0
- data/lib/redpear/namespace.rb +79 -0
- data/lib/redpear/nest.rb +13 -0
- data/lib/redpear/persistence.rb +134 -0
- data/lib/redpear/schema.rb +34 -0
- data/lib/redpear/schema/collection.rb +48 -0
- metadata +147 -0
data/lib/redpear.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "nest"
|
2
|
+
require "redis"
|
3
|
+
|
4
|
+
module Redpear
|
5
|
+
|
6
|
+
def self.autoload(const, path = nil)
|
7
|
+
path ||= "redpear/#{const.to_s.downcase}"
|
8
|
+
super const, path
|
9
|
+
end
|
10
|
+
|
11
|
+
autoload :Column
|
12
|
+
autoload :Concern
|
13
|
+
autoload :Connection
|
14
|
+
autoload :Expiration
|
15
|
+
autoload :Finders
|
16
|
+
autoload :Index
|
17
|
+
autoload :Model
|
18
|
+
autoload :Namespace
|
19
|
+
autoload :Nest
|
20
|
+
autoload :Persistence
|
21
|
+
autoload :Schema
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Redpear::Column < String
|
2
|
+
attr_reader :type, :model
|
3
|
+
|
4
|
+
# Creates a new column.
|
5
|
+
# @param [Redpear::Model] model the model the column is associated with
|
6
|
+
# @param [String] name the column name
|
7
|
+
# @param [Symbol] type the column type (:string (default), :counter, :integer, :timestamp)
|
8
|
+
def initialize(model, name, type = nil)
|
9
|
+
super name.to_s
|
10
|
+
@model = model
|
11
|
+
@type = type.to_sym if type
|
12
|
+
end
|
13
|
+
|
14
|
+
# Casts a value to a type
|
15
|
+
#
|
16
|
+
# @param value the value to cast
|
17
|
+
# @param [Symbol, #optional] type the type to cast to, defaults to the column type
|
18
|
+
# @return the casted value
|
19
|
+
def type_cast(value, type = self.type)
|
20
|
+
case type
|
21
|
+
when :counter
|
22
|
+
value.to_i
|
23
|
+
when :integer
|
24
|
+
Kernel::Integer(value) rescue nil if value
|
25
|
+
when :timestamp
|
26
|
+
value = type_cast(value, :integer)
|
27
|
+
Time.at(value) if value
|
28
|
+
else
|
29
|
+
value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String] the column name
|
34
|
+
def name
|
35
|
+
to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Boolean] true if the column is readable
|
39
|
+
def readable?
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Boolean] true if the column is writable
|
44
|
+
def writable?
|
45
|
+
type != :counter
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Boolean] true if the column is an index
|
49
|
+
def index?
|
50
|
+
is_a? Redpear::Index
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Redpear::Connection
|
2
|
+
extend Redpear::Concern
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
# @return [Redis] the current connection
|
7
|
+
def connection
|
8
|
+
@connection ||= (superclass.respond_to?(:connection) ? superclass.connection : Redis.current)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param [Redis] the connection to assign
|
12
|
+
def connection=(value)
|
13
|
+
@connection = value
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Hash stringify_keys extension. "Borrowed" from ActiveSupport.
|
2
|
+
class Hash
|
3
|
+
|
4
|
+
# Return a new hash with all keys converted to strings.
|
5
|
+
def stringify_keys
|
6
|
+
dup.stringify_keys!
|
7
|
+
end
|
8
|
+
|
9
|
+
# Destructively convert all keys to strings.
|
10
|
+
def stringify_keys!
|
11
|
+
keys.each do |key|
|
12
|
+
self[key.to_s] = delete(key) unless key.is_a?(String)
|
13
|
+
end
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
end unless Hash.new.respond_to?(:symbolize_keys)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Redpear::Expiration
|
2
|
+
|
3
|
+
# Expires the record.
|
4
|
+
# @param [Time, Integer] either a Time or an Integer period (in seconds)
|
5
|
+
def expire(value)
|
6
|
+
return false unless persisted?
|
7
|
+
|
8
|
+
case value
|
9
|
+
when Time
|
10
|
+
nest.expireat(value.to_i)
|
11
|
+
when Integer
|
12
|
+
nest.expire(value)
|
13
|
+
when String
|
14
|
+
value = Kernel::Integer(value) rescue nil
|
15
|
+
expire(value)
|
16
|
+
else
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Integer] the period this record has to live.
|
22
|
+
# May return -1 for non-expiring records and nil for non-persisted records.
|
23
|
+
def ttl
|
24
|
+
nest.ttl if persisted?
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Redpear::Finders
|
2
|
+
extend Redpear::Concern
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
# @return [Array] the IDs of all existing records
|
7
|
+
def members
|
8
|
+
mb_nest.smembers
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [Integer] the number of total records
|
12
|
+
def count
|
13
|
+
members.size
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Array] all records
|
17
|
+
def all
|
18
|
+
members.map {|id| find(id) }
|
19
|
+
end
|
20
|
+
|
21
|
+
# Finds a single record.
|
22
|
+
#
|
23
|
+
# @param id the ID of the record to retrieve
|
24
|
+
# @param [Hash] options additional options
|
25
|
+
# @option :lazy defaults to true, set to false to load the record instantly
|
26
|
+
# @return [Redpear::Model] a record, or nil when not found
|
27
|
+
def find(id, options = {})
|
28
|
+
record = instantiate('id' => id.to_s) # Initialize
|
29
|
+
if record.nest.exists # Do we have a record key?
|
30
|
+
record.refresh_attributes if options[:lazy] == false
|
31
|
+
record
|
32
|
+
else # Must be an expired or orphaned one
|
33
|
+
record.destroy # Destroy (removes from mb_nest set)
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param id the ID to check
|
39
|
+
# @return [Boolean] true or false
|
40
|
+
def exists?(id)
|
41
|
+
mb_nest.sismember(id)
|
42
|
+
end
|
43
|
+
|
44
|
+
def instantiate(*a)
|
45
|
+
new(*a).tap do |instance|
|
46
|
+
instance.send :instance_variable_set, :@__loaded__, false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Redpear::Index < Redpear::Column
|
2
|
+
|
3
|
+
# @return [Redpear::Nest] the namespace of the index. Example:
|
4
|
+
#
|
5
|
+
# index = Comment.columns.lookup["post_id"]
|
6
|
+
# index.namespace # => "comments:post_id"
|
7
|
+
#
|
8
|
+
def namespace
|
9
|
+
model.namespace[self]
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Redpear::Nest] the nest for a specific value. Example:
|
13
|
+
#
|
14
|
+
# index = Comment.columns.lookup["post_id"]
|
15
|
+
# index.nest(123) # => "comments:post_id:123"
|
16
|
+
# index.nest(nil) # => nil
|
17
|
+
# index.nest("") # => nil
|
18
|
+
#
|
19
|
+
def nest(value)
|
20
|
+
return nil if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
21
|
+
namespace[value]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'machinist'
|
2
|
+
|
3
|
+
# Machinist module for your tests/specs. Example:
|
4
|
+
#
|
5
|
+
# # spec/support/blueprints.rb
|
6
|
+
# require "redpear/machinist"
|
7
|
+
#
|
8
|
+
# Post.blueprint do
|
9
|
+
# title { "A Title" }
|
10
|
+
# created_at { 2.days.ago }
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
module Redpear::Machinist
|
14
|
+
|
15
|
+
class Blueprint < Machinist::Blueprint
|
16
|
+
|
17
|
+
def make!(attributes = {})
|
18
|
+
make(attributes).tap &:save
|
19
|
+
end
|
20
|
+
|
21
|
+
def lathe_class #:nodoc:
|
22
|
+
Lathe
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class Lathe < Machinist::Lathe
|
28
|
+
protected
|
29
|
+
|
30
|
+
def make_one_value(attribute, args)
|
31
|
+
return unless block_given?
|
32
|
+
raise_argument_error(attribute) unless args.empty?
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
|
36
|
+
def assign_attribute(key, value) #:nodoc:
|
37
|
+
@assigned_attributes[key.to_sym] = value
|
38
|
+
@object.load key => value
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Redpear::Model #:nodoc:
|
45
|
+
extend Machinist::Machinable
|
46
|
+
|
47
|
+
def self.blueprint_class
|
48
|
+
Redpear::Machinist::Blueprint
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require "set"
|
2
|
+
require "redpear/core_ext/stringify_keys"
|
3
|
+
|
4
|
+
=begin
|
5
|
+
Redis is a simple key/value store, hence storing structured data can be a
|
6
|
+
challenge. Redpear allows you to store/find/associate "records" in a Redis DB
|
7
|
+
very efficiently, minimising IO operations and storage space where possible.
|
8
|
+
|
9
|
+
For example:
|
10
|
+
|
11
|
+
class Post < Redpear::Model
|
12
|
+
column :title
|
13
|
+
column :body
|
14
|
+
end
|
15
|
+
|
16
|
+
class Comment < Redpear::Model
|
17
|
+
column :body
|
18
|
+
index :post_id
|
19
|
+
end
|
20
|
+
|
21
|
+
Let's create a post and a comment:
|
22
|
+
|
23
|
+
post = Post.save :title => "Hi!", :body => "I'm a new post"
|
24
|
+
comment = Comment.save :post_id => post.id, :body => "I like this!"
|
25
|
+
|
26
|
+
Redpear is VERY lightweight. Compared with other ORMs, it offers raw speed at
|
27
|
+
the expense of convenience.
|
28
|
+
=end
|
29
|
+
class Redpear::Model < Hash
|
30
|
+
include Redpear::Connection
|
31
|
+
include Redpear::Namespace
|
32
|
+
include Redpear::Persistence
|
33
|
+
include Redpear::Expiration
|
34
|
+
include Redpear::Schema
|
35
|
+
include Redpear::Finders
|
36
|
+
|
37
|
+
# Ensure we can read raw level values
|
38
|
+
alias_method :__fetch__, :[]
|
39
|
+
|
40
|
+
def initialize(attrs = {})
|
41
|
+
super()
|
42
|
+
@__attributes__ = {}
|
43
|
+
@__loaded__ = true
|
44
|
+
update(attrs)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Every record needs an ID
|
48
|
+
def id
|
49
|
+
value = __fetch__("id")
|
50
|
+
value.to_s if value
|
51
|
+
end
|
52
|
+
|
53
|
+
# Custom comparator
|
54
|
+
def ==(other)
|
55
|
+
case other
|
56
|
+
when Redpear::Model
|
57
|
+
other.instance_of?(self.class) && to_hash(true) == other.to_hash(true)
|
58
|
+
else
|
59
|
+
super
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Attribute reader with type-casting
|
64
|
+
def [](name)
|
65
|
+
__ensure_loaded__
|
66
|
+
name = name.to_s
|
67
|
+
@__attributes__[name] ||= begin
|
68
|
+
column = self.class.columns.lookup[name]
|
69
|
+
value = super(name)
|
70
|
+
column ? column.type_cast(value) : value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Attribute writer
|
75
|
+
def []=(name, value)
|
76
|
+
__ensure_loaded__
|
77
|
+
name = name.to_s
|
78
|
+
@__attributes__.delete(name)
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns a Hash with attributes
|
83
|
+
def to_hash(clean = false)
|
84
|
+
__ensure_loaded__
|
85
|
+
attrs = clean ? reject {|_, v| v.nil? } : self
|
86
|
+
{}.update(attrs)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Show information about this record
|
90
|
+
def inspect
|
91
|
+
__ensure_loaded__
|
92
|
+
"#<#{self.class.name} #{super}>"
|
93
|
+
end
|
94
|
+
|
95
|
+
# Bulk-update attributes
|
96
|
+
def update(attrs)
|
97
|
+
attrs = (attrs ? attrs.stringify_keys : {})
|
98
|
+
attrs["id"] = attrs["id"].to_s if attrs["id"]
|
99
|
+
super
|
100
|
+
end
|
101
|
+
alias_method :load, :update
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def __ensure_loaded__
|
106
|
+
refresh_attributes unless @__loaded__
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# Namespace organization for models. Example:
|
2
|
+
#
|
3
|
+
# class Comment < Model
|
4
|
+
# index :post_id
|
5
|
+
# end
|
6
|
+
# instance = Comment.save(:post_id => 2)
|
7
|
+
#
|
8
|
+
# Comment.connection.keys
|
9
|
+
# # => ['comments:1', 'comments:+', 'comments:*', 'comments:post_id:2']
|
10
|
+
#
|
11
|
+
# # Instance nesting
|
12
|
+
# instance.nest # => 'comments:1'
|
13
|
+
# instance.nest.mapped_hmget_all # => { "post_id" => "2" }
|
14
|
+
#
|
15
|
+
# # Member nesting
|
16
|
+
# Comment.mb_nest # "comments:*"
|
17
|
+
# Comment.mb_nest.smembers # => #<Set: {1}>
|
18
|
+
#
|
19
|
+
# # PK nesting
|
20
|
+
# Comment.pk_nest # "comments:+"
|
21
|
+
# Comment.pk_nest.get # 1 = last ID
|
22
|
+
#
|
23
|
+
# # Index nesting
|
24
|
+
# Comment.columns["post_id"].nest(2) # "comments:post_id:2"
|
25
|
+
# Comment.columns["post_id"].nest(2).smembers # #<Set: {1}>
|
26
|
+
#
|
27
|
+
module Redpear::Namespace
|
28
|
+
extend Redpear::Concern
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
|
32
|
+
# @return [Redpear::Nest] the namespace of this model, Example:
|
33
|
+
#
|
34
|
+
# Comment.namespace # => "comments":Nest
|
35
|
+
#
|
36
|
+
def namespace
|
37
|
+
@namespace ||= Redpear::Nest.new(scope, connection)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [String] the scope of this model. Example:
|
41
|
+
#
|
42
|
+
# Comment.scope # => "comments"
|
43
|
+
#
|
44
|
+
# Override if you want to use a differnet scope schema.
|
45
|
+
def scope
|
46
|
+
@scope ||= "#{name.split('::').last.downcase}s"
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Redpear::Nest] the nest for the members store. Example:
|
50
|
+
#
|
51
|
+
# Comment.mb_nest # => 'comments:*'
|
52
|
+
# Comment.mb_nest.smembers # => [1, 2, 3]
|
53
|
+
#
|
54
|
+
def mb_nest
|
55
|
+
@mb_nest ||= namespace["*"]
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Redpear::Nest] the nest for the primary-key incrementor. Example:
|
59
|
+
#
|
60
|
+
# Comment.pk_nest # => 'comments:+'
|
61
|
+
# Comment.pk_nest.get # => 0
|
62
|
+
# Comment.pk_nest.incr # => 1
|
63
|
+
#
|
64
|
+
def pk_nest
|
65
|
+
@pk_nest ||= namespace["+"]
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Redpear::Nest] the nest for the current record. Example:
|
71
|
+
#
|
72
|
+
# comment.nest # => 'comments:123'
|
73
|
+
# Comment.new.nest # => 'comments:_'
|
74
|
+
#
|
75
|
+
def nest
|
76
|
+
self.class.namespace[id || '_']
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
data/lib/redpear/nest.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# Redpear's persistence methods
|
2
|
+
module Redpear::Persistence
|
3
|
+
extend Redpear::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
# Runs a bulk-operation.
|
8
|
+
# @yield [] operations that should be run in the transaction
|
9
|
+
def transaction(&block)
|
10
|
+
connection.multi(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create or update a record. Example:
|
14
|
+
#
|
15
|
+
# Post.save :body => "Hello World!" # => creates a new Post
|
16
|
+
# Post.save :id => 3, :body => "Hello World!" # => updates an existing Post
|
17
|
+
#
|
18
|
+
def save(*args)
|
19
|
+
new(*args).tap(&:save)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Destroys a record. Example:
|
23
|
+
# @param id the ID of the record to destroy
|
24
|
+
# @return [Redpear::Model] the destroyed record
|
25
|
+
def destroy(id)
|
26
|
+
new('id' => id).tap(&:destroy)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Generates the next ID
|
30
|
+
def next_id
|
31
|
+
pk_nest.incr.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns true for new records
|
37
|
+
def new_record?
|
38
|
+
!id
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns true for existing records
|
42
|
+
def persisted?
|
43
|
+
!new_record?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Reloads the record (destructive)
|
47
|
+
def reload
|
48
|
+
replace self.class.find(id, :lazy => false) if persisted?
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# Load attributes from DB (destructive)
|
53
|
+
def refresh_attributes
|
54
|
+
update nest.mapped_hmget(*self.class.columns.names) if persisted?
|
55
|
+
@__loaded__ = true
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# Saves the record.
|
60
|
+
#
|
61
|
+
# @param [Hash] options additional options
|
62
|
+
# @option options [Integer|Date] :expire expiration period or timestamp
|
63
|
+
# @yield [record] Additional block, applied as part of the save transaction
|
64
|
+
# @return [Redpear::Model] the saved record
|
65
|
+
def save(options = {}, &block)
|
66
|
+
before_save
|
67
|
+
update "id" => self.class.next_id unless persisted?
|
68
|
+
|
69
|
+
transaction do
|
70
|
+
nest.mapped_hmset __persistable_attributes__
|
71
|
+
__relevant_sets__.each {|s| s.sadd(id) }
|
72
|
+
expire options[:expire]
|
73
|
+
yield(self) if block
|
74
|
+
end
|
75
|
+
ensure
|
76
|
+
after_save
|
77
|
+
end
|
78
|
+
|
79
|
+
# Destroy the record.
|
80
|
+
# @return [Boolean] true or false
|
81
|
+
def destroy
|
82
|
+
return false unless persisted?
|
83
|
+
|
84
|
+
transaction do
|
85
|
+
nest.del
|
86
|
+
__relevant_sets__.each {|s| s.srem(id) }
|
87
|
+
end
|
88
|
+
|
89
|
+
true
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
|
94
|
+
# Run in a DB transaction, returns self
|
95
|
+
def transaction(&block)
|
96
|
+
self.class.transaction(&block)
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
# "Cheap" callback, override in subclasses
|
101
|
+
def before_save
|
102
|
+
end
|
103
|
+
|
104
|
+
# "Cheap" callback, override in subclasses
|
105
|
+
def after_save
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Attributes that can be persisted
|
111
|
+
def __persistable_attributes__
|
112
|
+
result = {}
|
113
|
+
each do |key, value|
|
114
|
+
next if key == "id"
|
115
|
+
result[key] = __persistable_value__(value)
|
116
|
+
end
|
117
|
+
result
|
118
|
+
end
|
119
|
+
|
120
|
+
def __persistable_value__(value)
|
121
|
+
case value
|
122
|
+
when Time
|
123
|
+
value.to_i
|
124
|
+
else
|
125
|
+
value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Return relevant set nests
|
130
|
+
def __relevant_sets__
|
131
|
+
@__relevant_sets__ ||= [self.class.mb_nest] + self.class.columns.indices.map {|i| i.nest self[i] }.compact
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Redpear::Schema
|
2
|
+
extend Redpear::Concern
|
3
|
+
autoload :Collection, 'redpear/schema/collection'
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
# @return [Redpear::Schema::Collection] the columns of this model
|
8
|
+
def columns
|
9
|
+
@columns ||= Redpear::Schema::Collection.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [multiple] the column definition. Please see Redpear::Column#initialize
|
13
|
+
def column(*args)
|
14
|
+
columns.column(self, *args).tap do |col|
|
15
|
+
__define_attribute_accessors__(col)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [multiple] the index definition. Please see Redpear::Column#initialize
|
20
|
+
def index(*args)
|
21
|
+
columns.index(self, *args).tap do |col|
|
22
|
+
__define_attribute_accessors__(col)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def __define_attribute_accessors__(col)
|
29
|
+
define_method(col.name) { self[col.name] } if col.readable?
|
30
|
+
define_method("#{col.name}=") {|v| self[col.name] = v } if col.writable?
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Stores the column information
|
2
|
+
class Redpear::Schema::Collection < Array
|
3
|
+
|
4
|
+
# @param [multiple] the column definition. Please see Redpear::Column#initialize
|
5
|
+
def column(*args)
|
6
|
+
reset!
|
7
|
+
Redpear::Column.new(*args).tap do |col|
|
8
|
+
self << col
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [multiple] the index definition. Please see Redpear::Column#initialize
|
13
|
+
def index(*args)
|
14
|
+
reset!
|
15
|
+
Redpear::Index.new(*args).tap do |col|
|
16
|
+
self << col
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Array] the names of the columns
|
21
|
+
def names
|
22
|
+
@names ||= lookup.keys
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Array] the names of the indices only
|
26
|
+
def indices
|
27
|
+
@indices ||= select(&:index?)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Hash] the column lookup, indexed by name
|
31
|
+
def lookup
|
32
|
+
@lookup ||= inject({}) {|r, c| r.update c.to_s => c }
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [String] the column name
|
36
|
+
# @return [Redpear::Column] the column for the given name
|
37
|
+
def [](name)
|
38
|
+
lookup[name.to_s]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Resets indexes and lookups
|
42
|
+
def reset!
|
43
|
+
instance_variables.each do |name|
|
44
|
+
instance_variable_set name, nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redpear
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dimitrij Denissenko
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-24 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: redis
|
16
|
+
requirement: &13539180 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.2.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *13539180
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: nest
|
27
|
+
requirement: &13533640 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.1.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *13533640
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &13533060 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *13533060
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: &13532580 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *13532580
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rspec
|
60
|
+
requirement: &13531860 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *13531860
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: fakeredis
|
71
|
+
requirement: &13529820 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *13529820
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: shoulda-matchers
|
82
|
+
requirement: &13528760 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *13528760
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: machinist
|
93
|
+
requirement: &13527200 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ~>
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 2.0.0.beta
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *13527200
|
102
|
+
description: Simple, elegant & efficient ORM for Redis
|
103
|
+
email: dimitrij@blacksquaremedia.com
|
104
|
+
executables: []
|
105
|
+
extensions: []
|
106
|
+
extra_rdoc_files: []
|
107
|
+
files:
|
108
|
+
- lib/redpear.rb
|
109
|
+
- lib/redpear/connection.rb
|
110
|
+
- lib/redpear/schema/collection.rb
|
111
|
+
- lib/redpear/core_ext/stringify_keys.rb
|
112
|
+
- lib/redpear/concern.rb
|
113
|
+
- lib/redpear/namespace.rb
|
114
|
+
- lib/redpear/nest.rb
|
115
|
+
- lib/redpear/finders.rb
|
116
|
+
- lib/redpear/persistence.rb
|
117
|
+
- lib/redpear/expiration.rb
|
118
|
+
- lib/redpear/column.rb
|
119
|
+
- lib/redpear/model.rb
|
120
|
+
- lib/redpear/index.rb
|
121
|
+
- lib/redpear/schema.rb
|
122
|
+
- lib/redpear/machinist.rb
|
123
|
+
homepage: https://github.com/bsm/redpear
|
124
|
+
licenses: []
|
125
|
+
post_install_message:
|
126
|
+
rdoc_options: []
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - ! '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 1.8.7
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ! '>='
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: 1.6.0
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 1.8.10
|
144
|
+
signing_key:
|
145
|
+
specification_version: 3
|
146
|
+
summary: Redpear, a Redis ORM
|
147
|
+
test_files: []
|