redpear 0.3.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.
- 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: []
|