redis-orm 0.0.1
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/.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
|