curly_mustache 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|