redis-orm 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +189 -0
- data/Rakefile +1 -0
- data/lib/redis-orm.rb +32 -0
- data/lib/redis/actions.rb +21 -0
- data/lib/redis/actions/creating.rb +31 -0
- data/lib/redis/actions/destroying.rb +39 -0
- data/lib/redis/actions/finding.rb +26 -0
- data/lib/redis/actions/saving.rb +47 -0
- data/lib/redis/actions/timestamps.rb +19 -0
- data/lib/redis/actions/updating.rb +18 -0
- data/lib/redis/orm.rb +59 -0
- data/lib/redis/orm/attributes.rb +65 -0
- data/lib/redis/orm/version.rb +14 -0
- data/lib/redis/relations.rb +22 -0
- data/lib/redis/relations/belongs_to.rb +51 -0
- data/lib/redis/relations/has_many.rb +60 -0
- data/lib/redis/relations/has_one.rb +51 -0
- data/lib/redis/validations.rb +9 -0
- data/lib/redis/validations/uniqueness.rb +41 -0
- data/redis-orm.gemspec +25 -0
- data/spec/redis/actions/creating_spec.rb +32 -0
- data/spec/redis/actions/destroying_spec.rb +27 -0
- data/spec/redis/actions/finding_spec.rb +26 -0
- data/spec/redis/actions/timestamps_spec.rb +11 -0
- data/spec/redis/actions/updating_spec.rb +32 -0
- data/spec/redis/orm_spec.rb +56 -0
- data/spec/redis/relations/belongs_to_spec.rb +54 -0
- data/spec/redis/relations/has_many_spec.rb +33 -0
- data/spec/redis/relations/has_one_spec.rb +23 -0
- data/spec/redis/relations/interop_spec.rb +45 -0
- data/spec/redis/validations/uniqueness_spec.rb +32 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/active_model_lint.rb +20 -0
- data/spec/support/redis_helper.rb +27 -0
- data/test.rb +23 -0
- metadata +136 -0
data/lib/redis/orm.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.expand_path('../redis-orm', File.dirname(__FILE__))
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
require 'active_model'
|
4
|
+
require 'redis/relations'
|
5
|
+
require 'redis/validations'
|
6
|
+
require 'redis/orm/attributes'
|
7
|
+
require 'redis/actions'
|
8
|
+
|
9
|
+
class Redis::ORM
|
10
|
+
class_inheritable_accessor :serializer
|
11
|
+
self.serializer ||= Marshal
|
12
|
+
delegate :model_name, :to => "self.class"
|
13
|
+
delegate :connection, :to => :Redis
|
14
|
+
|
15
|
+
extend ActiveModel::Callbacks
|
16
|
+
extend ActiveModel::Naming
|
17
|
+
include ActiveModel::Validations
|
18
|
+
|
19
|
+
define_model_callbacks :initialize
|
20
|
+
|
21
|
+
include Redis::ORM::Attributes
|
22
|
+
include Redis::Actions
|
23
|
+
include Redis::Relations
|
24
|
+
include Redis::Validations
|
25
|
+
|
26
|
+
attribute :key
|
27
|
+
validates_uniqueness_of :key
|
28
|
+
|
29
|
+
class << self
|
30
|
+
delegate :connection, :to => :Redis
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_key
|
34
|
+
persisted? ? key : nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_param
|
38
|
+
persisted? ? File.join(model_name, key) : nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def persisted?
|
42
|
+
!new_record? && !changed?
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(attributes = {})
|
46
|
+
run_callbacks :initialize do
|
47
|
+
self.attributes = attributes
|
48
|
+
@previous_attributes = nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def transaction(&block)
|
53
|
+
connection.multi &block
|
54
|
+
end
|
55
|
+
|
56
|
+
def ==(other)
|
57
|
+
other && key == other.key
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class Redis::ORM
|
2
|
+
module Attributes
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
extend Redis::ORM::Attributes::ClassMethods
|
6
|
+
class_inheritable_hash :model_attributes
|
7
|
+
self.model_attributes ||= HashWithIndifferentAccess.new
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def attributes
|
12
|
+
@attributes ||= model_attributes.dup
|
13
|
+
end
|
14
|
+
|
15
|
+
def set_unchanged!
|
16
|
+
@previous_attributes = attributes.dup
|
17
|
+
end
|
18
|
+
|
19
|
+
def attribute_names
|
20
|
+
@attribute_names ||= attributes.keys
|
21
|
+
end
|
22
|
+
|
23
|
+
def attributes=(changed_attributes)
|
24
|
+
changed_attributes.each do |key, value|
|
25
|
+
send("#{key}=", value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def previous_attributes
|
30
|
+
# note we do NOT assign here, this is because #changed?
|
31
|
+
# and #new_record? rely on @previous_attributes to be nil
|
32
|
+
@previous_attributes || attributes.dup
|
33
|
+
end
|
34
|
+
|
35
|
+
def new_record?
|
36
|
+
@previous_attributes.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def changed?
|
40
|
+
new_record? || attributes != @previous_attributes
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
def attribute_names
|
45
|
+
model_attributes.keys
|
46
|
+
end
|
47
|
+
|
48
|
+
def attribute(key, default_value = nil)
|
49
|
+
model_attributes.merge!({key => default_value})
|
50
|
+
|
51
|
+
define_method key do
|
52
|
+
attributes[key]
|
53
|
+
end
|
54
|
+
|
55
|
+
define_method "#{key}=" do |value|
|
56
|
+
if value != attributes[key]
|
57
|
+
attributes[key] = value
|
58
|
+
else
|
59
|
+
value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Redis::Relations
|
2
|
+
autoload :BelongsTo, "redis/relations/belongs_to"
|
3
|
+
autoload :HasMany, "redis/relations/has_many"
|
4
|
+
autoload :HasOne, "redis/relations/has_one"
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
class << self
|
9
|
+
def add_relation(name)
|
10
|
+
varn = "#{name}_relations"
|
11
|
+
class_inheritable_hash varn # class_inheritable_hash 'belongs_to_relations'
|
12
|
+
send(varn) || send("#{varn}=", {}) # self.belongs_to_relations ||= {}
|
13
|
+
within_save_block "save_#{name}_references" # within_save_block 'save_belongs_to_references'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
include Redis::Relations::BelongsTo
|
18
|
+
include Redis::Relations::HasMany
|
19
|
+
include Redis::Relations::HasOne
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Redis::Relations::BelongsTo
|
2
|
+
def belongs_to_references
|
3
|
+
@belongs_to_references ||= {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def set_belongs_to_reference(name, a)
|
7
|
+
belongs_to_references[name] = a
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_belongs_to_reference(name)
|
11
|
+
if belongs_to_references.key?(name)
|
12
|
+
belongs_to_references[name]
|
13
|
+
else
|
14
|
+
result = self.class.find(connection.hget(belongs_to_relation_key(name), key))
|
15
|
+
belongs_to_references[name] = result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def belongs_to_relation_key(name)
|
20
|
+
File.join("references", belongs_to_relations[name][:relation].to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
def save_belongs_to_references
|
24
|
+
belongs_to_references.each do |relation_name, reference|
|
25
|
+
if reference
|
26
|
+
reference = reference.key
|
27
|
+
end
|
28
|
+
connection.hset(belongs_to_relation_key(relation_name), key, reference)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.included(base)
|
33
|
+
base.class_eval do
|
34
|
+
add_relation :belongs_to
|
35
|
+
|
36
|
+
class << self
|
37
|
+
def belongs_to(relation_name, options = {})
|
38
|
+
belongs_to_relations[relation_name] = options.reverse_merge({ :relation => relation_name })
|
39
|
+
|
40
|
+
define_method relation_name do
|
41
|
+
get_belongs_to_reference(relation_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
define_method "#{relation_name}=" do |a|
|
45
|
+
set_belongs_to_reference(relation_name, a)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Redis::Relations::HasMany
|
2
|
+
def has_many_references
|
3
|
+
@has_many_references ||= {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def get_has_many_reference(relation_name)
|
7
|
+
if has_many_references.key?(relation_name)
|
8
|
+
has_many_references[relation_name]
|
9
|
+
else
|
10
|
+
result = connection.hget(has_many_relation_key(relation_name), key)
|
11
|
+
if result
|
12
|
+
result = serializer.load(result)
|
13
|
+
if result.respond_to?(:collect)
|
14
|
+
result = result.collect { |r| self.class.find(r) }
|
15
|
+
else
|
16
|
+
result = [find(result)]
|
17
|
+
end
|
18
|
+
else
|
19
|
+
result = []
|
20
|
+
end
|
21
|
+
has_many_references[relation_name] = result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def save_has_many_references
|
26
|
+
has_many_references.each do |relation_name, array|
|
27
|
+
array = array.collect { |a| a.key }
|
28
|
+
connection.hset(has_many_relation_key(relation_name), key, serializer.dump(array))
|
29
|
+
array.each do |akey|
|
30
|
+
connection.hset(has_many_relation_key(relation_name), akey, key)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def has_many_relation_key(name)
|
36
|
+
File.join("references", has_many_relations[name][:relation].to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.included(base)
|
40
|
+
base.class_eval do
|
41
|
+
add_relation :has_many
|
42
|
+
|
43
|
+
class << self
|
44
|
+
def has_many(relation_name, options = {})
|
45
|
+
has_many_relations[relation_name] = options.reverse_merge({ :relation => relation_name })
|
46
|
+
|
47
|
+
define_method relation_name do
|
48
|
+
get_has_many_reference(relation_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
define_method "#{relation_name}=" do |array|
|
52
|
+
target = get_has_many_reference(relation_name)
|
53
|
+
target.clear
|
54
|
+
target.concat array
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Redis::Relations::HasOne
|
2
|
+
def has_one_references
|
3
|
+
@has_one_references ||= {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def set_has_one_reference(relation_name, value)
|
7
|
+
has_one_references[relation_name] = value
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_has_one_reference(relation_name)
|
11
|
+
if has_one_references.key?(relation_name)
|
12
|
+
has_one_references[relation_name]
|
13
|
+
else
|
14
|
+
result = self.class.find(connection.hget(has_one_relation_key(relation_name), key))
|
15
|
+
has_one_references[relation_name] = result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def has_one_relation_key(name)
|
20
|
+
File.join("references", has_one_relations[name][:relation].to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
def save_has_one_references
|
24
|
+
has_one_references.each do |relation_name, reference|
|
25
|
+
if reference
|
26
|
+
connection.hset(has_one_relation_key(relation_name), key, reference.key)
|
27
|
+
connection.hset(has_one_relation_key(relation_name), reference.key, key)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.included(base)
|
33
|
+
base.class_eval do
|
34
|
+
add_relation :has_one
|
35
|
+
|
36
|
+
class << self
|
37
|
+
def has_one(relation_name, options = {})
|
38
|
+
has_one_relations[relation_name] = options.reverse_merge({ :relation => relation_name })
|
39
|
+
|
40
|
+
define_method relation_name do
|
41
|
+
get_has_one_reference(relation_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
define_method "#{relation_name}=" do |a|
|
45
|
+
set_has_one_reference(relation_name, a)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Redis::Validations::Uniqueness
|
2
|
+
def uniqueness_key(attribute_name)
|
3
|
+
File.join(model_name, attribute_name.to_s.pluralize)
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
class_inheritable_array :unique_fields
|
9
|
+
self.unique_fields ||= []
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def validates_uniqueness_of(field)
|
13
|
+
unique_fields << field
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
validate do |record|
|
18
|
+
record.unique_fields.each do |name|
|
19
|
+
if key_in_use = connection.hget(record.uniqueness_key(name), record.send(name))
|
20
|
+
if key_in_use != record.key
|
21
|
+
record.errors.add(name, "must be unique")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
within_save_block do |record|
|
28
|
+
record.unique_fields.each do |name|
|
29
|
+
connection.hdel(record.uniqueness_key(name), record.previous_attributes[name])
|
30
|
+
connection.hset(record.uniqueness_key(name), record.send(name), record.key)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
within_destroy_block do |record|
|
35
|
+
record.unique_fields.each do |name|
|
36
|
+
connection.hdel(record.uniqueness_key(name), record.send(name))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/redis-orm.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "redis/orm/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "redis-orm"
|
7
|
+
s.version = Redis::ORM::VERSION
|
8
|
+
s.authors = ["Colin MacKenzie IV"]
|
9
|
+
s.email = ["sinisterchipmunk@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/sinisterchipmunk/redis-orm"
|
11
|
+
s.summary = %q{Object-relational mapping for Redis}
|
12
|
+
s.description = %q{The goal of this project is to provide ORM for Redis that feels very similar to ActiveRecord.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "redis-orm"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency 'rspec', '~> 2.6.0'
|
22
|
+
|
23
|
+
s.add_runtime_dependency 'redis', '~> 2.2.2'
|
24
|
+
s.add_runtime_dependency 'activemodel', '~> 3.0.10'
|
25
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Redis::Actions::Creating do
|
4
|
+
include RedisHelper
|
5
|
+
|
6
|
+
orm_class { attribute :number }
|
7
|
+
|
8
|
+
it "should fire callbacks" do
|
9
|
+
before, after = 0, 0
|
10
|
+
orm_class { before_create { before += 1 }; after_create { after += 1 } }
|
11
|
+
|
12
|
+
orm_class.create!
|
13
|
+
before.should == 1
|
14
|
+
after.should == 1
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should create and return the record" do
|
18
|
+
record = orm_class.create(:number => 1)
|
19
|
+
record.should be_kind_of(orm_class)
|
20
|
+
|
21
|
+
record.should_not be_new_record
|
22
|
+
record.should_not be_changed
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should create! and return the record" do
|
26
|
+
record = orm_class.create!(:number => 1)
|
27
|
+
record.should be_kind_of(orm_class)
|
28
|
+
|
29
|
+
record.should_not be_new_record
|
30
|
+
record.should_not be_changed
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Redis::Actions::Destroying do
|
4
|
+
include RedisHelper
|
5
|
+
|
6
|
+
context "with several pre-existing records" do
|
7
|
+
orm_class { attribute :random }
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
10.times { |i| orm_class.new(:random => i).save! }
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should destroy all of them" do
|
14
|
+
orm_class.destroy_all
|
15
|
+
orm_class.all.length.should == 0
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should fire destroy callbacks for all of them" do
|
19
|
+
before, after = 0, 0
|
20
|
+
orm_class { before_destroy { before += 1 }; after_destroy { after += 1 } }
|
21
|
+
orm_class.destroy_all
|
22
|
+
|
23
|
+
before.should == 10
|
24
|
+
after.should == 10
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|