curly_mustache 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 cjbottaro
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,106 @@
1
+ = CurlyMustache
2
+
3
+ ActiveModel implementation for various data stores. In other words, it provides an ActiveRecord like library for data stores other than relational databases. Out of the box, it works with Memcached, Redis, Tokyo Tyrant, Cassandra and Amazon SDB.
4
+
5
+ http://github.com/cjbottaro/curly_mustache
6
+
7
+ == Installation
8
+
9
+ sudo gem install curly_mustache
10
+
11
+ CurlyMustache depends on activemodel-3.0.0beta1 and activesupport-3.0.0beta1 which are unfortunately in a state of great flux and are thus very buggy. I'm hosting patched versions that are known to work with CurlyMustache here:
12
+
13
+ {activesupport-3.0.0beta1}[http://dl.dropbox.com/u/167916/stochasticbytes/activesupport-3.0.0.beta1.gem]
14
+
15
+ {activemodel-3.0.0beta1}[http://dl.dropbox.com/u/167916/stochasticbytes/activemodel-3.0.0.beta1.gem]
16
+
17
+ == Features
18
+
19
+ * Basic ActiveRecord-like CRUD operations (new, create, destroy, find, save).
20
+ * Validations (via ActiveModel).
21
+ * Callbacks (via ActiveModel).
22
+ * Attribute change tracking (via ActiveModel::Dirty).
23
+ * Easy creation of new types.
24
+ * Easy creation of new adapters (to connect to different data stores).
25
+ * 100% rcov(erage)!
26
+
27
+ == Quickstart
28
+
29
+ If your data store speaks Memcached, then you just need to use the <tt>:memcached</tt> adapter.
30
+
31
+ CurlyMustache::Base.establish_connection :adapter => :memcached,
32
+ :servers => %w[one.example.com:11211 two.example.com:11211]
33
+
34
+ class User < CurlyMustache::Base
35
+ attribute :name, :string
36
+ attribute :login_count, :integer
37
+ attribute :birthday, :time
38
+
39
+ validates_presence_of :name
40
+ after_validation :capitalize_name
41
+
42
+ def capitalize_name
43
+ self.name = name.capitalize
44
+ end
45
+ end
46
+
47
+ user = User.new :name => "chris"
48
+ user.new_record? # => true
49
+ user.id # => nil
50
+ user.name # => "chris"
51
+ user.birthday # => nil
52
+ user.birthday = "03/11/1980"
53
+ user.birthday_changed? # => true
54
+ user.birthday.class # => Time
55
+ user.birthday # => "Tue Mar 11 00:00:00 -0600 1980"
56
+ user.save!
57
+ user.id # => "676cef021584904876af7c4b3e42afb5"
58
+
59
+ user = User.find(user.id)
60
+ user.name # => "Chris"
61
+
62
+ user.destroy
63
+ User.find(user.id) # => CurlyMustache::RecordNotFound
64
+ end
65
+
66
+ The +memcache+ adapter uses memcache-client[http://github.com/mperham/memcache-client]. Whatever other options you pass to +establish_connection+ will be passed to the constructor for it.
67
+
68
+ == Types
69
+
70
+ Five types are defined for you: <tt>string</tt>, <tt>integer</tt>, <tt>float</tt>, <tt>time</tt>, <tt>boolean</tt>. It is easy to define new types or modify existing ones. See {Read more}[link:/classes/CurlyMustache/Attributes/Types.html]
71
+
72
+ == Callbacks
73
+
74
+ The following callbacks are available:
75
+ before_validation_on_create after_validation_on_create
76
+ before_validation_on_update after_validation_on_update
77
+ before_validation after_validation
78
+ before_create after_create
79
+ before_update after_update
80
+ before_save after_save
81
+ before_destroy after_destroy
82
+ after_find
83
+
84
+ == Adapters
85
+
86
+ You can use the {Memcached adapter}[link:/classes/CurlyMustache/Adapters/Memcached.html] with any data stores that speak memcached. There is also the {Cassandra adapter}[link:/classes/CurlyMustache/Adapters/Cassandra.html] and soon to be SDB adapter and Tokyo Tyrant table type adapter.
87
+
88
+ You can create your own adapters by subclassing CurlyMustache::Adapters::Abstract and implementing a few methods.
89
+
90
+ == Serializing
91
+
92
+ If you need to serialize your model's attributes before sending them to the adapter, it's done by overriding {send_attributes}[link:/classes/CurlyMustache/Connection/InstanceMethods.html#M000078]. For deserialization, override {recv_attributes}[/classes/CurlyMustache/Connection/InstanceMethods.html#M000079].
93
+
94
+ This is how data flows in and out of the data store...
95
+
96
+ --------------------- -------------------------- --------------- --------------
97
+ | record.attributes | ==> | record.send_attributes | ==> | adapter.put | ==> | data store |
98
+ --------------------- -------------------------- --------------- --------------
99
+
100
+ --------------------- -------------------------- --------------- --------------
101
+ | record.attributes | <== | record.recv_attributes | <== | adapter.get | <== | data store |
102
+ --------------------- -------------------------- --------------- --------------
103
+
104
+
105
+ == Author
106
+ Christopher J. Bottaro - {cjbottaro}[http://github.com/cjbottaro]
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "curly_mustache"
8
+ gem.summary = %Q{ActiveRecord-like interface to various data stores, using ActiveModel.}
9
+ gem.email = "cjbottaro@alumni.cs.utexas.edu"
10
+ gem.homepage = "http://github.com/cjbottaro/curly_mustache"
11
+ gem.authors = ["Christopher J Bottaro"]
12
+
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.pattern = 'test/**/*_test.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'lib' << 'test'
30
+ test.pattern = 'test/**/*_test.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+
40
+ task :default => :test
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ if File.exist?('VERSION.yml')
45
+ config = YAML.load(File.read('VERSION.yml'))
46
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "curly_mustache #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
56
+
data/TODO ADDED
File without changes
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 0
@@ -0,0 +1,89 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{curly_mustache}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Christopher J Bottaro"]
12
+ s.date = %q{2010-02-24}
13
+ s.email = %q{cjbottaro@alumni.cs.utexas.edu}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.rdoc",
17
+ "TODO"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "TODO",
26
+ "VERSION.yml",
27
+ "curly_mustache.gemspec",
28
+ "lib/curly_mustache.rb",
29
+ "lib/curly_mustache/adapters.rb",
30
+ "lib/curly_mustache/adapters/abstract.rb",
31
+ "lib/curly_mustache/adapters/cassandra.rb",
32
+ "lib/curly_mustache/adapters/memcached.rb",
33
+ "lib/curly_mustache/attributes.rb",
34
+ "lib/curly_mustache/attributes/definition.rb",
35
+ "lib/curly_mustache/attributes/manager.rb",
36
+ "lib/curly_mustache/attributes/types.rb",
37
+ "lib/curly_mustache/base.rb",
38
+ "lib/curly_mustache/connection.rb",
39
+ "lib/curly_mustache/crud.rb",
40
+ "lib/curly_mustache/default_types.rb",
41
+ "lib/curly_mustache/errors.rb",
42
+ "lib/curly_mustache/locking.rb",
43
+ "test/abstract_adapter_test.rb",
44
+ "test/adapters.yml",
45
+ "test/attributes_test.rb",
46
+ "test/callbacks_test.rb",
47
+ "test/crud_test.rb",
48
+ "test/locking_test.rb",
49
+ "test/models/account.rb",
50
+ "test/models/feed.rb",
51
+ "test/models/page.rb",
52
+ "test/models/user.rb",
53
+ "test/serialization_test.rb",
54
+ "test/test_helper.rb",
55
+ "test/types_test.rb",
56
+ "test/validations_test.rb"
57
+ ]
58
+ s.homepage = %q{http://github.com/cjbottaro/curly_mustache}
59
+ s.rdoc_options = ["--charset=UTF-8"]
60
+ s.require_paths = ["lib"]
61
+ s.rubygems_version = %q{1.3.6}
62
+ s.summary = %q{ActiveRecord-like interface to various data stores, using ActiveModel.}
63
+ s.test_files = [
64
+ "test/abstract_adapter_test.rb",
65
+ "test/attributes_test.rb",
66
+ "test/callbacks_test.rb",
67
+ "test/crud_test.rb",
68
+ "test/locking_test.rb",
69
+ "test/models/account.rb",
70
+ "test/models/feed.rb",
71
+ "test/models/page.rb",
72
+ "test/models/user.rb",
73
+ "test/serialization_test.rb",
74
+ "test/test_helper.rb",
75
+ "test/types_test.rb",
76
+ "test/validations_test.rb"
77
+ ]
78
+
79
+ if s.respond_to? :specification_version then
80
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
81
+ s.specification_version = 3
82
+
83
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
84
+ else
85
+ end
86
+ else
87
+ end
88
+ end
89
+
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+
3
+ gem "activemodel", "3.0.0.beta1"
4
+
5
+ require "active_support"
6
+ require "active_support/core_ext"
7
+ require "active_model"
8
+
9
+ require "digest"
10
+ require "ostruct"
11
+
12
+ require "curly_mustache/adapters"
13
+ require "curly_mustache/errors"
14
+ require "curly_mustache/connection"
15
+ require "curly_mustache/attributes"
16
+ require "curly_mustache/crud"
17
+ require "curly_mustache/base"
18
+
19
+ # Load all the default adapters
20
+ require "curly_mustache/adapters/memcached"
21
+ CurlyMustache::Adapters.register(CurlyMustache::Adapters::Memcached)
22
+ require "curly_mustache/adapters/cassandra"
23
+ CurlyMustache::Adapters.register(CurlyMustache::Adapters::Cassandra)
@@ -0,0 +1,61 @@
1
+ require "curly_mustache/adapters/abstract"
2
+
3
+ module CurlyMustache
4
+ # Adapters are how CurlyMustache talks to various data stores.
5
+ #
6
+ # You define new adapters by subclassing CurlyMustache::Adapters::Abstract and then calling <tt>CurlyMustache::Adapters.register</tt>.
7
+ # At a bare minimum, you need to implement +initialize+, +get+, +put+, +delete+.
8
+ #
9
+ # Ex:
10
+ # class MyFunkyAdapter < CurlyMustache::Adapters::Abstract
11
+ #
12
+ # # config is the same hash that is passed to #establish_connection.
13
+ # def initialize(config)
14
+ # @client = FunkyDataStoreClient.new(config)
15
+ # end
16
+ #
17
+ # def get(key)
18
+ # @client.retrieve(key)
19
+ # end
20
+ #
21
+ # def put(key, value)
22
+ # @client.store(key, value)
23
+ # end
24
+ #
25
+ # def delete(key)
26
+ # @client.destroy(key)
27
+ # end
28
+ # end
29
+ #
30
+ # # Now we register the adapter so we can use it in #establish_connection.
31
+ # CurlyMustache::Adapters.register(MyFunkyAdapter)
32
+ #
33
+ # # We can see a list of registered adapters.
34
+ # CurlyMustache::Adapters.list # => { :my_funky_adapter => MyFunkyAdapter }
35
+ #
36
+ # # The hash passed to #establish_connection is passed as config to #initialize.
37
+ # CurlyMustache::Base.establish_connection :adapter => :my_funky_adapter, :server => "localhost", :option1 => "blah"
38
+ module Adapters
39
+
40
+ # Register an adapter class so it can be referenced by name by +establish_connection+.
41
+ # If the class you are registering is <tt>MyModule::MyClass</tt> then the name it will be registered
42
+ # under will simply be <tt>:my_class</tt>.
43
+ def self.register(klass)
44
+ @registered_adapters ||= {}
45
+ @registered_adapters[klass.name.split("::").last.underscore.to_sym] = klass
46
+ end
47
+
48
+ # Returns of hash of all registered adapters where the keys are the registered names.
49
+ def self.list
50
+ @registered_adapters.dup
51
+ end
52
+
53
+ # Get an adapter by registered name.
54
+ def self.get(name)
55
+ name = name.to_sym
56
+ raise ArgumentError, "adapter #{name} is not registered" unless @registered_adapters.has_key?(name)
57
+ @registered_adapters[name]
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,86 @@
1
+ module CurlyMustache
2
+ module Adapters
3
+ # Subclass this class to make new adapters.
4
+ #
5
+ # The required methods for basic functionality are: +new+, +get+, +put+ and +delete+.
6
+ #
7
+ # If you want to include CurlyMustache::Locking, then you must implement +lock+ and +unlock+ and <tt>locked?</tt>.
8
+ #
9
+ # If you want unit tests to run using your adapter, you must implement +flush_db+.
10
+ class Abstract
11
+ # Holds a reference to the class that is currently using this adapter.
12
+ attr_accessor :model_class
13
+
14
+ ############
15
+ # REQUIRED #
16
+ ############
17
+
18
+ # Implementing this is required for basic functionality.
19
+ # +config+ is passed directly from {#establish_connection}[link:/classes/CurlyMustache/Connection/ClassMethods.html#M000089].
20
+ def initialize(config)
21
+ raise NotImplementedError
22
+ end
23
+
24
+ # Implementing this is required for basic functionality.
25
+ # The output should be a hash of attributes that will be used to instantiate a model,
26
+ # or the input of an "in" serializer if one is being used.
27
+ def get(key)
28
+ raise NotImplementedError
29
+ end
30
+
31
+ # Implementing this is required for basic functionality.
32
+ # +value+ is the model's attributes hash, or the output of an "out" serializer if one is being used.
33
+ def put(key, value)
34
+ raise NotImplementedError
35
+ end
36
+
37
+ # Implementing this is required for basic functionality.
38
+ def delete(key)
39
+ raise NotImplementedError
40
+ end
41
+
42
+ ############
43
+ # OPTIONAL #
44
+ ############
45
+
46
+ # Implement this if you want unit tests to run for your adapter.
47
+ def flush_db
48
+ raise NotImplementedError
49
+ end
50
+
51
+ # Implement this if you want to include the <tt>CurlyMustache::Locking<tt> module into your model class.
52
+ def lock(key)
53
+ raise NotImplementedError
54
+ end
55
+
56
+ # Implement this if you want to include the <tt>CurlyMustache::Locking<tt> module into your model class.
57
+ def unlock(key)
58
+ raise NotImplementedError
59
+ end
60
+
61
+ # Implement this if you want to include the <tt>CurlyMustache::Locking<tt> module into your model class.
62
+ def locked?(key)
63
+ raise NotImplementedError
64
+ end
65
+
66
+ ############
67
+ # PROVIDED #
68
+ ############
69
+
70
+ # The default implementation of this just iterates over keys calling +get+ on them. Override this
71
+ # implementation if your data store has a more efficient way of retrieving multiple keys. The
72
+ # return value should be an array of values where each value follow the same rules for the output
73
+ # of +get+. Also the order of the values in the array should match the order of the +keys+ argument.
74
+ def mget(keys)
75
+ keys.collect{ |key| get(key) }.compact
76
+ end
77
+
78
+ # This returns the name of the adapter which can be used in {#establish_connection}[link:/classes/CurlyMustache/Connection/ClassMethods.html#M000089].
79
+ # If your adapter class's name is "MyFunkyAdapter", then this method will return <tt>:my_funky_adapter</tt>.
80
+ def adapter_name
81
+ @adapter_name ||= self.class.name.split("::").last.underscore.to_sym
82
+ end
83
+
84
+ end
85
+ end
86
+ end