curly_mustache 0.1.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/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +106 -0
- data/Rakefile +56 -0
- data/TODO +0 -0
- data/VERSION.yml +4 -0
- data/curly_mustache.gemspec +89 -0
- data/lib/curly_mustache.rb +23 -0
- data/lib/curly_mustache/adapters.rb +61 -0
- data/lib/curly_mustache/adapters/abstract.rb +86 -0
- data/lib/curly_mustache/adapters/cassandra.rb +35 -0
- data/lib/curly_mustache/adapters/memcached.rb +56 -0
- data/lib/curly_mustache/attributes.rb +85 -0
- data/lib/curly_mustache/attributes/definition.rb +24 -0
- data/lib/curly_mustache/attributes/manager.rb +41 -0
- data/lib/curly_mustache/attributes/types.rb +49 -0
- data/lib/curly_mustache/base.rb +36 -0
- data/lib/curly_mustache/connection.rb +65 -0
- data/lib/curly_mustache/crud.rb +244 -0
- data/lib/curly_mustache/default_types.rb +18 -0
- data/lib/curly_mustache/errors.rb +14 -0
- data/lib/curly_mustache/locking.rb +83 -0
- data/test/abstract_adapter_test.rb +22 -0
- data/test/adapters.yml +6 -0
- data/test/attributes_test.rb +96 -0
- data/test/callbacks_test.rb +22 -0
- data/test/crud_test.rb +169 -0
- data/test/locking_test.rb +57 -0
- data/test/models/account.rb +31 -0
- data/test/models/feed.rb +13 -0
- data/test/models/page.rb +4 -0
- data/test/models/user.rb +10 -0
- data/test/serialization_test.rb +22 -0
- data/test/test_helper.rb +35 -0
- data/test/types_test.rb +13 -0
- data/test/validations_test.rb +65 -0
- metadata +112 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'cassandra'
|
2
|
+
|
3
|
+
module CurlyMustache
|
4
|
+
module Adapters
|
5
|
+
class Cassandra < Abstract
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
@client = ::Cassandra.new(options[:keyspace], options[:servers])
|
9
|
+
@column_family = options[:column_family]
|
10
|
+
end
|
11
|
+
|
12
|
+
def column_family
|
13
|
+
@column_family || model_class.name.pluralize.to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
def put(key, value)
|
17
|
+
@client.insert(column_family, key, value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def get(key)
|
21
|
+
result = @client.get(column_family, key)
|
22
|
+
result.empty? ? nil : result
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(key)
|
26
|
+
@client.remove(column_family, key)
|
27
|
+
end
|
28
|
+
|
29
|
+
def flush_db
|
30
|
+
@client.clear_keyspace!
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'memcache'
|
2
|
+
|
3
|
+
module CurlyMustache
|
4
|
+
module Adapters
|
5
|
+
# You can use this adapter with any data store that speaks Memcached. The adapter uses
|
6
|
+
# {memcache-client}[http://github.com/mperham/memcache-client]. The <tt>:servers</tt> key in
|
7
|
+
# the hash passed to CurlyMustache::Base#establish_connection will be the first argument to
|
8
|
+
# <tt>MemCache.new</tt> and entire hash will be passed as the second argument.
|
9
|
+
class Memcached < Abstract
|
10
|
+
|
11
|
+
# <tt>config[:servers]</tt> will be passed as the first argument to <tt>MemCache.new</tt> and
|
12
|
+
# <tt>config</tt> itself will be passed as the second argument.
|
13
|
+
def initialize(config)
|
14
|
+
config = config.reverse_merge :servers => "localhost:11211"
|
15
|
+
@cache = MemCache.new(config[:servers], config)
|
16
|
+
end
|
17
|
+
|
18
|
+
def get(key)
|
19
|
+
@cache.get(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def mget(keys)
|
23
|
+
keys = keys.collect(&:to_s)
|
24
|
+
results = @cache.get_multi(*keys)
|
25
|
+
results = results.collect{ |k, v| [k, v] }
|
26
|
+
results.sort.collect{ |result| result[1] }
|
27
|
+
end
|
28
|
+
|
29
|
+
def put(key, value)
|
30
|
+
@cache.set(key, value)
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete(key)
|
34
|
+
@cache.delete(key)
|
35
|
+
end
|
36
|
+
|
37
|
+
def flush_db
|
38
|
+
@cache.flush_all
|
39
|
+
end
|
40
|
+
|
41
|
+
def lock(key, options = {})
|
42
|
+
expires_in = options[:expires_in] || 0
|
43
|
+
@cache.add(key, Time.now.to_s(:number), expires_in) == "STORED\r\n"
|
44
|
+
end
|
45
|
+
|
46
|
+
def unlock(key)
|
47
|
+
delete(key) == "DELETED\r\n"
|
48
|
+
end
|
49
|
+
|
50
|
+
def locked?(key)
|
51
|
+
!!@cache.get(key)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "curly_mustache/attributes/manager"
|
2
|
+
|
3
|
+
module CurlyMustache
|
4
|
+
|
5
|
+
# It looks like typecasting happens at assignment for ActiveRecord, so we're just going to follow that.
|
6
|
+
# user.account_id = "test"
|
7
|
+
# user.account_id
|
8
|
+
# => 0
|
9
|
+
module Attributes
|
10
|
+
|
11
|
+
def self.included(mod)
|
12
|
+
mod.class_eval do
|
13
|
+
class_inheritable_accessor :attribute_manager
|
14
|
+
class_inheritable_accessor :allow_settable_id
|
15
|
+
end
|
16
|
+
mod.attribute_manager = Manager.new
|
17
|
+
mod.send(:extend, ClassMethods)
|
18
|
+
mod.send(:include, InstanceMethods)
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
|
23
|
+
def attribute(name, type, options = {})
|
24
|
+
attribute_manager.define(self, name, type, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def attributes
|
28
|
+
attribute_manager.definitions
|
29
|
+
end
|
30
|
+
|
31
|
+
def attribute_type(name)
|
32
|
+
attribute_manager[name].type
|
33
|
+
end
|
34
|
+
|
35
|
+
def allow_settable_id!(settable = true)
|
36
|
+
self.allow_settable_id = settable
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
module InstanceMethods
|
42
|
+
|
43
|
+
def attributes=(hash)
|
44
|
+
hash.stringify_keys.each{ |k, v| write_attribute(k, v) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def attributes
|
48
|
+
@attributes.dup
|
49
|
+
end
|
50
|
+
|
51
|
+
def read_attribute(name)
|
52
|
+
@attributes[name.to_s]
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_attribute(name, value)
|
56
|
+
send("#{name}_will_change!") # ActiveModel::Dirty
|
57
|
+
@attributes[name.to_s] = value
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_attribute_with_typecast(name, value)
|
61
|
+
casted_value = attribute_manager[name].cast(value)
|
62
|
+
write_attribute_without_typecast(name, casted_value)
|
63
|
+
end
|
64
|
+
|
65
|
+
alias_method_chain :write_attribute, :typecast
|
66
|
+
|
67
|
+
def write_attribute_with_id_guard(name, value)
|
68
|
+
raise IdNotSettableError, "not allowed to set id" if name.to_s == "id" and !allow_settable_id
|
69
|
+
write_attribute_without_id_guard(name, value)
|
70
|
+
end
|
71
|
+
|
72
|
+
alias_method_chain :write_attribute, :id_guard
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# This is like #attributes= but allows for setting the id. It's intended to be used
|
77
|
+
# internally by methods like #read.
|
78
|
+
def set_attributes(hash)
|
79
|
+
hash.stringify_keys.each{ |k, v| write_attribute_without_id_guard(k, v) }
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CurlyMustache
|
2
|
+
module Attributes
|
3
|
+
class Definition
|
4
|
+
attr_reader :name, :type
|
5
|
+
|
6
|
+
def initialize(name, type, options = {})
|
7
|
+
@options = options.symbolize_keys.reverse_merge :default => nil,
|
8
|
+
:allow_nil => true
|
9
|
+
@name = name.to_sym
|
10
|
+
@type = type.to_sym
|
11
|
+
@caster = Types[type].caster
|
12
|
+
end
|
13
|
+
|
14
|
+
def cast(value)
|
15
|
+
if value.nil? and @options[:allow_nil]
|
16
|
+
nil
|
17
|
+
else
|
18
|
+
@caster.call(value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "curly_mustache/attributes/types"
|
2
|
+
require "curly_mustache/attributes/definition"
|
3
|
+
|
4
|
+
module CurlyMustache
|
5
|
+
module Attributes
|
6
|
+
class Manager
|
7
|
+
attr_reader :definitions
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@definitions = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def define(klass, name, type, options = {})
|
14
|
+
@definitions[name.to_s] = Definition.new(name, type, options)
|
15
|
+
|
16
|
+
klass.class_eval <<-eval
|
17
|
+
def #{name}; read_attribute(:#{name}); end
|
18
|
+
def #{name}=(value); write_attribute(:#{name}, value); end
|
19
|
+
eval
|
20
|
+
|
21
|
+
# This is so ghetto, but these are the hoops we have to jump through
|
22
|
+
# to get ActiveModel::Dirty working with inheritance.
|
23
|
+
klass.undefine_attribute_methods
|
24
|
+
klass.define_attribute_methods(@definitions.keys.collect(&:to_sym))
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](name)
|
28
|
+
name = name.to_s
|
29
|
+
raise AttributeNotDefinedError, "#{name} is not defined" unless @definitions.has_key?(name)
|
30
|
+
@definitions[name]
|
31
|
+
end
|
32
|
+
|
33
|
+
def dup
|
34
|
+
returning(self.class.new) do |new_manager|
|
35
|
+
new_manager.instance_variable_set("@definitions", @definitions.dup)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module CurlyMustache
|
2
|
+
module Attributes
|
3
|
+
# <tt>CurlyMustache</tt> comes with 5 types predefined: string, integer, float, time, boolean.
|
4
|
+
# You can redefine any of them or add new type defintions. To define a type is simply to define
|
5
|
+
# how a value gets typecasted.
|
6
|
+
# CurlyMustache::Attributes::Types.define(:capitalized_string) do |value|
|
7
|
+
# value.capitalize
|
8
|
+
# end
|
9
|
+
# Now if you have a user class...
|
10
|
+
# class User < CurlyMustache::Base
|
11
|
+
# attribute :name, :string
|
12
|
+
# attribute :title, :capitalized_string
|
13
|
+
# end
|
14
|
+
# And you can see the new type in action...
|
15
|
+
# user = User.new
|
16
|
+
# user.name = "chris"
|
17
|
+
# user.title = "mr"
|
18
|
+
# user.name # => "chris"
|
19
|
+
# user.title # => "Mr"
|
20
|
+
# user.title = 123 # NoMethodError: undefined method `capitalize' for 123:Fixnum
|
21
|
+
module Types
|
22
|
+
|
23
|
+
# Gets a hash of all type defintions. The keys will be the type names and they will always be strings.
|
24
|
+
def self.definitions
|
25
|
+
@definitions ||= {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Clear all type defintions (including the defaults).
|
29
|
+
def self.clear
|
30
|
+
@definitions = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Define a type. The block takes a single argument which is the raw value and should return
|
34
|
+
# the typecasted value.
|
35
|
+
def self.define(name, &block)
|
36
|
+
definitions[name.to_s] = OpenStruct.new(:name => name, :caster => block)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Similar to <tt>CurlyMustache::Attributes::Types.defintions[name]</tt> but is indifferent to
|
40
|
+
# whether +name+ is a string or symbol and will raise an exception if +name+ is not a defined.
|
41
|
+
def self.[](name)
|
42
|
+
name = name.to_s
|
43
|
+
raise TypeError, "type #{name} is not defined" unless definitions.has_key?(name)
|
44
|
+
definitions[name]
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "curly_mustache/default_types"
|
2
|
+
|
3
|
+
module CurlyMustache
|
4
|
+
class Base
|
5
|
+
|
6
|
+
include Connection
|
7
|
+
include Attributes
|
8
|
+
include Crud
|
9
|
+
|
10
|
+
extend ActiveModel::Callbacks
|
11
|
+
include ActiveModel::Validations
|
12
|
+
include ActiveModel::Dirty
|
13
|
+
|
14
|
+
define_model_callbacks :create, :destroy, :save, :update, :validation, :validation_on_create, :validation_on_update, :only => [:before, :after]
|
15
|
+
define_model_callbacks :find, :only => :after
|
16
|
+
|
17
|
+
# Set this to true if you want to set your own ids as opposed to having CurlyMustache
|
18
|
+
# automatically generate them for you. Ex:
|
19
|
+
# class User
|
20
|
+
# self.allow_settable_id = true
|
21
|
+
# attribute :name, :string
|
22
|
+
# end
|
23
|
+
# User.create(:id => 123, :name => "blah")
|
24
|
+
# User.find(123)
|
25
|
+
allow_settable_id!(false)
|
26
|
+
|
27
|
+
attribute :id, :string
|
28
|
+
|
29
|
+
def ==(other)
|
30
|
+
self.attributes == other.attributes and
|
31
|
+
self.new_record? == other.new_record?
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module CurlyMustache
|
2
|
+
# NOTE: The way this is implemented makes CurlyMustache not thread safe!
|
3
|
+
#
|
4
|
+
# You are probably looking for {establish_connection}[link:/classes/CurlyMustache/Connection/ClassMethods.html#M000084].
|
5
|
+
module Connection
|
6
|
+
|
7
|
+
def self.included(mod) # :nodoc:
|
8
|
+
mod.class_eval do
|
9
|
+
class_inheritable_accessor :_connection
|
10
|
+
end
|
11
|
+
mod.send(:extend, ClassMethods)
|
12
|
+
mod.send(:include, InstanceMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
# Establishes a connection using the adapter specified in <tt>config[:adapter]</tt>.
|
18
|
+
# If you call +establish_connection+ on CurlyMustache::Base, then all models will
|
19
|
+
# use that connection unless +establish_connection+ is called directly on a model class.
|
20
|
+
# Note that +config+ itself is passed to the adapter's constructor.
|
21
|
+
#
|
22
|
+
# Ex:
|
23
|
+
# CurlyMustache::Base.establish_connection(:adapter => :memcached, :servers => "localhost:11211")
|
24
|
+
def establish_connection(config)
|
25
|
+
config = config.symbolize_keys
|
26
|
+
self._connection = Adapters.get(config[:adapter]).new(config)
|
27
|
+
end
|
28
|
+
|
29
|
+
def connection # :nodoc:
|
30
|
+
_connection.model_class = self
|
31
|
+
_connection
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
module InstanceMethods
|
37
|
+
|
38
|
+
# Override this method if you want to massage the data that is sent to the adapter.
|
39
|
+
#
|
40
|
+
# +attributes+ is the same as <tt>self.attributes</tt>.
|
41
|
+
#
|
42
|
+
# Return value will be sent to the adapter's +put+ method.
|
43
|
+
def send_attributes(attributes)
|
44
|
+
attributes
|
45
|
+
end
|
46
|
+
|
47
|
+
# Override this method if you want to massage the data that is received from the adapter.
|
48
|
+
#
|
49
|
+
# +attributes+ is what is returned from the adapter's +get+ method.
|
50
|
+
#
|
51
|
+
# Return value will be assigned to <tt>self.attributes</tt>.
|
52
|
+
def recv_attributes(attributes)
|
53
|
+
attributes
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def connection # :nodoc:
|
59
|
+
self.class.connection
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
module CurlyMustache
|
2
|
+
|
3
|
+
module Crud
|
4
|
+
|
5
|
+
def self.included(mod) # :nodoc:
|
6
|
+
mod.send(:extend, ClassMethods)
|
7
|
+
mod.send(:include, InstanceMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
# Create a record and save it to the data store. Returns a record with errors if validation fails.
|
13
|
+
def create(attributes = {})
|
14
|
+
returning(new) do |record|
|
15
|
+
record.attributes = attributes
|
16
|
+
record.save
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Create a record and save it to the data store. Raises RecordInvalid if validation fails.
|
21
|
+
def create!(attributes = {})
|
22
|
+
returning(create(attributes)) do |record|
|
23
|
+
record.errors.count > 0 and raise(RecordInvalid, "Validation failed: #{record.errors.full_messages.join(', ')}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Find by id. Can take multiple ids. Raise RecordNotFound if not all ids are found.
|
28
|
+
def find(*ids)
|
29
|
+
ids = [ids].flatten
|
30
|
+
if ids.length == 1
|
31
|
+
find_one(ids.first)
|
32
|
+
else
|
33
|
+
find_many(ids, :raise)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Find multiple records by ids. May return an array with less records
|
38
|
+
# than ids asked for or an empty array.
|
39
|
+
def find_all_by_id(*ids)
|
40
|
+
ids = [ids].flatten
|
41
|
+
find_many(ids)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Find a single record by id. Returns nil if record is not found.
|
45
|
+
def find_by_id(id)
|
46
|
+
find_one(id)
|
47
|
+
rescue RecordNotFound
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
# Deletes records by ids without instantiating them first, thus the
|
52
|
+
# *_destroy callbacks won't be invoked.
|
53
|
+
def delete_all(*ids)
|
54
|
+
ids_to_keys(ids).each{ |key| connection.delete(key) }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Instantiate records then calls destroy on them.
|
58
|
+
def destroy_all(*ids)
|
59
|
+
find(ids).each{ |record| record.destroy }
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def id_to_key(id)
|
65
|
+
raise NoKeyError if id.blank?
|
66
|
+
"#{self}:#{id}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def ids_to_keys(ids)
|
70
|
+
[ids].flatten.collect{ |id| id_to_key(id) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_one(id)
|
74
|
+
raise RecordNotFound, "Couldn't find #{name} without an ID" if id.blank?
|
75
|
+
new.send(:read, :id => id)
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_many(ids, should_raise = false)
|
79
|
+
hashes = connection.mget(ids_to_keys(ids))
|
80
|
+
if should_raise and ids.length != hashes.length
|
81
|
+
raise RecordNotFound, find_many_error_message(ids, hashes)
|
82
|
+
else
|
83
|
+
ids.zip(hashes).collect do |id, attributes|
|
84
|
+
record = new
|
85
|
+
record.send(:read, :attributes => record.send(:recv_attributes, attributes))
|
86
|
+
record
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def find_many_error_message(ids, hashes)
|
92
|
+
ids_string = ids.join(",")
|
93
|
+
models_name = name.pluralize
|
94
|
+
found, wanted = hashes.length, ids.length
|
95
|
+
"Couldn't find all #{models_name} with IDs (#{ids_string}) (found #{found} results, but was looking for #{wanted})"
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
module InstanceMethods
|
101
|
+
|
102
|
+
# Make a new record in memory with supplied attributes.
|
103
|
+
def initialize(attributes = {})
|
104
|
+
@attributes = {}
|
105
|
+
@new_record = true
|
106
|
+
self.attributes = attributes
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns true if the record has been saved yet.
|
110
|
+
def new_record?
|
111
|
+
!!@new_record
|
112
|
+
end
|
113
|
+
|
114
|
+
# Reload the record from the data store, overwriting any attribute changes.
|
115
|
+
def reload
|
116
|
+
returning(self){ read }
|
117
|
+
end
|
118
|
+
|
119
|
+
# Save the record to the data store. Returns false if validation fails.
|
120
|
+
def save
|
121
|
+
new_record? ? create : update
|
122
|
+
(errors.count > 0) ? false : self
|
123
|
+
end
|
124
|
+
|
125
|
+
# Save the record to the data store. Raises RecordInvalid if validation fails.
|
126
|
+
def save!
|
127
|
+
returning(save) do
|
128
|
+
errors.count > 0 and raise(RecordInvalid, "Validation failed: #{errors.full_messages.join(', ')}")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Delete a record from the data store, invoking the *_destroy callbacks.
|
133
|
+
def destroy
|
134
|
+
delete
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def generate_id
|
140
|
+
Digest::MD5.hexdigest(rand.to_s + Time.now.to_s)
|
141
|
+
end
|
142
|
+
|
143
|
+
def id_to_key(id)
|
144
|
+
self.class.send(:id_to_key, id)
|
145
|
+
end
|
146
|
+
|
147
|
+
def key
|
148
|
+
id_to_key(id)
|
149
|
+
end
|
150
|
+
|
151
|
+
def create
|
152
|
+
@attributes["id"] = generate_id if id.blank?
|
153
|
+
update_without_callbacks
|
154
|
+
end
|
155
|
+
|
156
|
+
def create_with_callbacks
|
157
|
+
_run_validation_on_create_callbacks do
|
158
|
+
_run_validation_callbacks do
|
159
|
+
valid? or return
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
_run_create_callbacks do
|
164
|
+
_run_save_callbacks do
|
165
|
+
create_without_callbacks
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
alias_method_chain :create, :callbacks
|
171
|
+
|
172
|
+
def read(options = {})
|
173
|
+
options = options.reverse_merge :id => nil,
|
174
|
+
:attributes => nil,
|
175
|
+
:keep_new => false
|
176
|
+
|
177
|
+
if options[:attributes]
|
178
|
+
set_attributes(options[:attributes])
|
179
|
+
else
|
180
|
+
if options[:id]
|
181
|
+
_id, _key = options[:id], id_to_key(options[:id])
|
182
|
+
else
|
183
|
+
_id, _key = id, key
|
184
|
+
end
|
185
|
+
attributes = recv_attributes(connection.get(_key)) || raise(RecordNotFound, "Couldn't find #{self.class.name} with ID=#{_id}")
|
186
|
+
set_attributes(attributes)
|
187
|
+
end
|
188
|
+
|
189
|
+
@new_record = options[:keep_new]
|
190
|
+
|
191
|
+
self
|
192
|
+
end
|
193
|
+
|
194
|
+
def read_with_callbacks(*args)
|
195
|
+
_run_find_callbacks do
|
196
|
+
read_without_callbacks(*args)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
alias_method_chain :read, :callbacks
|
201
|
+
|
202
|
+
def update
|
203
|
+
connection.put(key, send_attributes(attributes))
|
204
|
+
@new_record = false
|
205
|
+
|
206
|
+
# ActiveModel::Dirty
|
207
|
+
previously_changed_attributes.replace(changes)
|
208
|
+
changed_attributes.clear
|
209
|
+
end
|
210
|
+
|
211
|
+
def update_with_callbacks
|
212
|
+
_run_validation_on_update_callbacks do
|
213
|
+
_run_validation_callbacks do
|
214
|
+
valid? or return
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
_run_update_callbacks do
|
219
|
+
_run_save_callbacks do
|
220
|
+
update_without_callbacks
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
alias_method_chain :update, :callbacks
|
226
|
+
|
227
|
+
def delete
|
228
|
+
connection.delete(key)
|
229
|
+
freeze
|
230
|
+
end
|
231
|
+
|
232
|
+
def delete_with_callbacks
|
233
|
+
_run_destroy_callbacks do
|
234
|
+
delete_without_callbacks
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
alias_method_chain :delete, :callbacks
|
239
|
+
|
240
|
+
end # end module InstanceMethods
|
241
|
+
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|