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.
Files changed (38) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +4 -0
  3. data/README.md +189 -0
  4. data/Rakefile +1 -0
  5. data/lib/redis-orm.rb +32 -0
  6. data/lib/redis/actions.rb +21 -0
  7. data/lib/redis/actions/creating.rb +31 -0
  8. data/lib/redis/actions/destroying.rb +39 -0
  9. data/lib/redis/actions/finding.rb +26 -0
  10. data/lib/redis/actions/saving.rb +47 -0
  11. data/lib/redis/actions/timestamps.rb +19 -0
  12. data/lib/redis/actions/updating.rb +18 -0
  13. data/lib/redis/orm.rb +59 -0
  14. data/lib/redis/orm/attributes.rb +65 -0
  15. data/lib/redis/orm/version.rb +14 -0
  16. data/lib/redis/relations.rb +22 -0
  17. data/lib/redis/relations/belongs_to.rb +51 -0
  18. data/lib/redis/relations/has_many.rb +60 -0
  19. data/lib/redis/relations/has_one.rb +51 -0
  20. data/lib/redis/validations.rb +9 -0
  21. data/lib/redis/validations/uniqueness.rb +41 -0
  22. data/redis-orm.gemspec +25 -0
  23. data/spec/redis/actions/creating_spec.rb +32 -0
  24. data/spec/redis/actions/destroying_spec.rb +27 -0
  25. data/spec/redis/actions/finding_spec.rb +26 -0
  26. data/spec/redis/actions/timestamps_spec.rb +11 -0
  27. data/spec/redis/actions/updating_spec.rb +32 -0
  28. data/spec/redis/orm_spec.rb +56 -0
  29. data/spec/redis/relations/belongs_to_spec.rb +54 -0
  30. data/spec/redis/relations/has_many_spec.rb +33 -0
  31. data/spec/redis/relations/has_one_spec.rb +23 -0
  32. data/spec/redis/relations/interop_spec.rb +45 -0
  33. data/spec/redis/validations/uniqueness_spec.rb +32 -0
  34. data/spec/spec_helper.rb +12 -0
  35. data/spec/support/active_model_lint.rb +20 -0
  36. data/spec/support/redis_helper.rb +27 -0
  37. data/test.rb +23 -0
  38. metadata +136 -0
@@ -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,14 @@
1
+ class Redis
2
+ class ORM
3
+ module Version
4
+ MAJOR = 0
5
+ MINOR = 0
6
+ PATCH = 1
7
+ PREREL = nil
8
+
9
+ STRING = PREREL ? [MAJOR, MINOR, PATCH, PREREL].join('.') : [MAJOR, MINOR, PATCH].join('.')
10
+ end
11
+
12
+ VERSION = Version::STRING
13
+ end
14
+ 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,9 @@
1
+ module Redis::Validations
2
+ autoload :Uniqueness, "redis/validations/uniqueness"
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ include Redis::Validations::Uniqueness
7
+ end
8
+ end
9
+ 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
@@ -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