rubysouth-tokyo_model 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,9 @@
1
+ == 0.0.2 2009-04-17
2
+
3
+ * 1 major enhancement:
4
+ * Added adapters for TDB files and Tokyo Tyrant.
5
+
6
+ == 0.0.1 2009-04-15
7
+
8
+ * 1 major enhancement:
9
+ * Project set up on Github
data/Manifest.txt ADDED
@@ -0,0 +1,18 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ lib/tokyo_model.rb
6
+ lib/tokyo_model/adapters/abstract_adapter.rb
7
+ lib/tokyo_model/adapters/file.rb
8
+ lib/tokyo_model/adapters/tyrant.rb
9
+ lib/tokyo_model/persistable.rb
10
+ script/console
11
+ script/destroy
12
+ script/generate
13
+ test/contest.rb
14
+ test/fixtures/post.rb
15
+ test/test_basic_model.rb
16
+ test/test_file_adapter.rb
17
+ test/test_helper.rb
18
+ tokyo_model.gemspec
data/README.rdoc ADDED
@@ -0,0 +1,32 @@
1
+ = TokyoModel
2
+
3
+ TokyoModel is a lightweight ORM that uses TokyoCabinet's table databases (TDB)
4
+ as a storage backend. TDB has tables but no schemas, which allows for some
5
+ interesting possibilities in an ORM.
6
+
7
+ Our first public release is planned for some time in late April 2009. At the
8
+ moment almost ALMOST NOTHING is implemented. It wouldn't even be fair to
9
+ describe this as pre-alpha. DO NOT USE THIS.
10
+
11
+ == LICENSE:
12
+
13
+ Copyright (c) 2009 Adrian Mugnolo and Norman Clarke
14
+
15
+ Permission is hereby granted, free of charge, to any person obtaining
16
+ a copy of this software and associated documentation files (the
17
+ 'Software'), to deal in the Software without restriction, including
18
+ without limitation the rights to use, copy, modify, merge, publish,
19
+ distribute, sublicense, and/or sell copies of the Software, and to
20
+ permit persons to whom the Software is furnished to do so, subject to
21
+ the following conditions:
22
+
23
+ The above copyright notice and this permission notice shall be
24
+ included in all copies or substantial portions of the Software.
25
+
26
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
27
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
29
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
30
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
31
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
32
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'newgem'
3
+ require File.dirname(__FILE__) + '/lib/tokyo_model'
4
+
5
+ $hoe = Hoe.new('tokyo_model', TokyoModel::VERSION) do |p|
6
+ p.developer('Adrian Mugnolo', 'adrian@mugnolo.com')
7
+ p.developer('Norman Clarke', 'norman@randomba.org')
8
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
+ p.rubyforge_name = "tokyo-model"
10
+ p.url = "http://github.com/rubysouth/tokyo_model"
11
+ p.extra_dev_deps = [
12
+ ['newgem', ">= #{::Newgem::VERSION}"]
13
+ ]
14
+ end
15
+
16
+ require 'newgem/tasks'
@@ -0,0 +1,44 @@
1
+ module TokyoModel
2
+ module Adapters
3
+
4
+ # This class exists almost exclusively to indicate the methods that all
5
+ # adapters must implement. The default implementation simply delegates
6
+ # everything to the underlying database file or connection.
7
+ class AbstractAdapter
8
+
9
+ attr :db
10
+
11
+ # Delete the record identified by +pkey+.
12
+ def out(pkey)
13
+ @db.out(pkey)
14
+ end
15
+ alias_method :delete, :out
16
+
17
+ # Generate a new primary key to use as an identifier for a new record.
18
+ def genuid
19
+ @db.genuid
20
+ end
21
+
22
+ # Fetch the record identified by +pkey+.
23
+ def get(pkey)
24
+ @db.get(pkey)
25
+ end
26
+
27
+ # Insert or update the record identified by +pkey+.
28
+ def put(pkey, hash)
29
+ @db.put(pkey, hash)
30
+ end
31
+
32
+ # Perform a query.
33
+ def query(*args)
34
+ raise NotImplementedError.new
35
+ end
36
+
37
+ # Delegate any unknown method calls to the underlying @db.
38
+ def method_missing(symbol, *args)
39
+ @db.send(symbol, *args)
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,50 @@
1
+ require 'tokyocabinet'
2
+
3
+ module TokyoModel
4
+ module Adapters
5
+
6
+ # This adapter provides access to TokyoCabinet TDB files.
7
+ class File < AbstractAdapter
8
+
9
+
10
+ # Permitted open modes for accessing TDB files: +:read+, +:write+,
11
+ # +:create+, +:truncate+, +:nolock+, +:lock_noblock+ and +:sync+
12
+ OPEN_MODES = {
13
+ :read => TokyoCabinet::TDB::OREADER,
14
+ :write => TokyoCabinet::TDB::OWRITER,
15
+ :create => TokyoCabinet::TDB::OCREAT,
16
+ :truncate => TokyoCabinet::TDB::OTRUNC,
17
+ :nolock => TokyoCabinet::TDB::ONOLCK,
18
+ :lock_noblock => TokyoCabinet::TDB::OLCKNB,
19
+ :sync => TokyoCabinet::TDB::OTSYNC
20
+ }.freeze
21
+
22
+ # Open the TDB file. Accepts a URI and list of open modes:
23
+ #
24
+ # db = TokyoModel::Adapters::File.new("file:///tmp/database.tdb", :read,
25
+ # :write, :create, :trunc)
26
+ def initialize(uri, *args)
27
+ @db = TokyoCabinet::TDB::new
28
+ @db.open(uri.path, args.empty? ? File.default_open_mode : File.open_mode(*args))
29
+ end
30
+
31
+ class << self
32
+
33
+ # The default open mode is read/write.
34
+ def default_open_mode
35
+ open_mode(:read, :write)
36
+ end
37
+
38
+ # Takes an array of open mode symbols and returns the mode number:
39
+ #
40
+ # open_mode(:read, :write, :sync) # 3
41
+ # open_mode(:read, :write, :sync) # 67
42
+ def open_mode(*modes)
43
+ modes.inject(0) { |memo, obj| memo | OPEN_MODES[obj.to_sym].to_i }
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,27 @@
1
+ require 'tokyotyrant'
2
+
3
+ module TokyoModel
4
+ module Adapters
5
+
6
+ # This adapter provides access to TokyoTyrant TDB databases.
7
+ class Tyrant < AbstractAdapter
8
+
9
+ DEFAULT_PORT = 1978
10
+
11
+ # Open the TDB file. Accepts a URI and discards any extra arguments
12
+ # passed in.
13
+ #
14
+ # db = TokyoModel::Adapters::Tyrant.new("tyrant://localhost:1978")
15
+ # db = TokyoModel::Adapters::Tyrant.new("unix://host/var/sock/tyrant.sock")
16
+ def initialize(uri, *args)
17
+ @db = TokyoTyrant::RDBTBL::new
18
+ if uri.scheme == "unix"
19
+ @db.open(uri.path, 0)
20
+ else
21
+ @db.open(uri.host, uri.port || DEFAULT_PORT)
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,65 @@
1
+ module TokyoModel
2
+
3
+ module Persistable
4
+
5
+ def self.included(base)
6
+ base.class_eval { extend TokyoModel::Persistable::ClassMethods }
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ def connect(uri, *args)
12
+ @db = TokyoModel.open(uri, *args)
13
+ end
14
+
15
+ def db
16
+ @db ||= TokyoModel::DATABASES.first
17
+ end
18
+
19
+ def get(id)
20
+ obj = new
21
+ if record = db.get(id)
22
+ record.each { |k, v| obj.send("#{k}=".to_sym, v) }
23
+ obj.id = id
24
+ end
25
+ obj
26
+ end
27
+ alias_method :find, :get
28
+
29
+ def setter_methods
30
+ instance_methods.select {|m| m =~ /[^=]$/ && instance_methods.include?("#{m}=") && !%w(taguri).include?(m) }
31
+ end
32
+
33
+ end
34
+
35
+ def ==(obj)
36
+ self.class == obj.class && id == obj.id
37
+ end
38
+
39
+ def attributes
40
+ self.class.setter_methods.inject({}) do |m, o|
41
+ v = send(o.to_sym)
42
+ m[o] = v if v
43
+ m
44
+ end
45
+ end
46
+
47
+ def db
48
+ self.class.db
49
+ end
50
+
51
+ def id
52
+ @id ||= db.genuid
53
+ end
54
+
55
+ def id=(id)
56
+ @id = id
57
+ end
58
+
59
+ def put
60
+ db.put(id, attributes)
61
+ end
62
+ alias_method :save, :put
63
+
64
+ end
65
+ end
@@ -0,0 +1,43 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ require 'uri'
4
+ require 'tokyo_model/persistable'
5
+ require 'tokyo_model/adapters/abstract_adapter'
6
+
7
+ module TokyoModel
8
+
9
+ VERSION = '0.0.2'
10
+ ADAPTERS = [:file, :tyrant].freeze
11
+ DATABASES = []
12
+
13
+ # Connect to the database. Accepts a uri and optional list of open modes:
14
+ #
15
+ # TokyoModel.connect("file:/tmp/database.tdb", :read, :write, :create, :truncate)
16
+ # TokyoModel.connect("tyrant://localhost:1978")
17
+ # TokyoModel.connect("unix://host/var/sock/tyrant.sock")
18
+ #
19
+ # The permitted URI schemas are: +file+, +tyrant+ and +unix+.
20
+ def self.open(*args)
21
+ uri = URI::parse(args.shift)
22
+ DATABASES << adapter(uri.scheme).new(uri, *args)
23
+ end
24
+
25
+ def self.close
26
+ DATABASES.each { |d| d.close }
27
+ end
28
+
29
+ private
30
+
31
+ def self.adapter(scheme)
32
+ class_name = scheme == "unix" ? "tyrant" : scheme
33
+ require "tokyo_model/adapters/#{class_name}"
34
+ TokyoModel::Adapters.const_get(class_name.titleize.to_sym)
35
+ end
36
+
37
+ end
38
+
39
+ class String
40
+ def titleize
41
+ self.gsub(/\b([a-z])/) { |x| x[-1..-1].upcase }
42
+ end
43
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ libs << " -r rubygems"
7
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/tokyo_model.rb'}"
8
+
9
+ puts "Loading tokyo_model gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
data/test/contest.rb ADDED
@@ -0,0 +1,99 @@
1
+ # The "Contest" library was taken from: http://github.com/citrusbyte/contest/
2
+ #
3
+ # Copyright (c) 2009 Damian Janowski and Michel Martens for Citrusbyte
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require "test/unit"
24
+
25
+ # Test::Unit loads a default test if the suite is empty, whose purpose is to
26
+ # fail. Since having empty contexts is a common practice, we decided to
27
+ # overwrite TestSuite#empty? in order to allow them. Having a failure when no
28
+ # tests have been defined seems counter-intuitive.
29
+ class Test::Unit::TestSuite
30
+ def empty?
31
+ false
32
+ end
33
+ end
34
+
35
+ # Contest adds +setup+, +teardown+, +test+ and +context+ as class methods, and
36
+ # the instance methods +setup+ and +teardown+ now iterate on the corresponding
37
+ # blocks. Note that all setup and teardown blocks must be defined with the
38
+ # block syntax. Adding setup or teardown instance methods defeats the purpose
39
+ # of this library.
40
+ class Test::Unit::TestCase
41
+ def self.setup(&block)
42
+ setup_blocks << block
43
+ end
44
+
45
+ def self.teardown(&block)
46
+ teardown_blocks << block
47
+ end
48
+
49
+ def setup
50
+ self.class.setup_blocks.each do |block|
51
+ instance_eval(&block)
52
+ end
53
+ end
54
+
55
+ def teardown
56
+ self.class.teardown_blocks.each do |block|
57
+ instance_eval(&block)
58
+ end
59
+ end
60
+
61
+ def self.context(name, &block)
62
+ subclass = Class.new(self.superclass)
63
+ subclass.setup_blocks.unshift(*setup_blocks)
64
+ subclass.teardown_blocks.unshift(*teardown_blocks)
65
+ subclass.class_eval(&block)
66
+ const_set(context_name(name), subclass)
67
+ end
68
+
69
+ def self.test(name, &block)
70
+ define_method(test_name(name), &block)
71
+ end
72
+
73
+ class << self
74
+ alias_method :should, :test
75
+ alias_method :describe, :context
76
+ end
77
+
78
+ private
79
+
80
+ def self.setup_blocks
81
+ @setup_blocks ||= []
82
+ end
83
+
84
+ def self.teardown_blocks
85
+ @teardown_blocks ||= []
86
+ end
87
+
88
+ def self.context_name(name)
89
+ "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}".to_sym
90
+ end
91
+
92
+ def self.test_name(name)
93
+ "test_#{sanitize_name(name).gsub(/\s+/,'_')}".to_sym
94
+ end
95
+
96
+ def self.sanitize_name(name)
97
+ name.gsub(/\W+/, ' ').strip
98
+ end
99
+ end
@@ -0,0 +1,4 @@
1
+ class Post
2
+ include TokyoModel::Persistable
3
+ attr_accessor :title, :body, :author, :permalink
4
+ end
@@ -0,0 +1,48 @@
1
+ require 'test_helper'
2
+ require File.dirname(__FILE__) + "/fixtures/post.rb"
3
+
4
+ class TestBasicModel < Test::Unit::TestCase
5
+
6
+ setup do
7
+ TokyoModel.open("file:#{dbpath}", :write, :read, :create, :truncate)
8
+ @post = Post.new
9
+ @post.title = "First post!"
10
+ @post.body = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"
11
+ @post.author = "John Doe"
12
+ @post.permalink = "http://example.org/posts/1"
13
+ @post.put
14
+ end
15
+
16
+ teardown do
17
+ TokyoModel.close
18
+ FileUtils.rm_f dbpath
19
+ end
20
+
21
+ context "a model" do
22
+
23
+ should "have a db connection" do
24
+ assert_not_nil Post.db
25
+ end
26
+
27
+ should "be gettable" do
28
+ post2 = Post.get(@post.id)
29
+ assert_equal @post.title, post2.title
30
+ assert_equal 1, TokyoModel::DATABASES.first.rnum
31
+ end
32
+
33
+ should "generate an id when persisted" do
34
+ post = Post.new
35
+ post.title = "test"
36
+ post.put
37
+ assert_not_nil post.id
38
+ end
39
+
40
+ end
41
+
42
+ context "a model instance" do
43
+ should "be equal to another instance of the same class with the same id" do
44
+ assert_equal @post, Post.get(@post.id)
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,9 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class TestFileAdapter < Test::Unit::TestCase
4
+
5
+ test "default open mode should be read/write" do
6
+ assert_equal 3, TokyoModel::Adapters::File.default_open_mode
7
+ end
8
+
9
+ end
@@ -0,0 +1,17 @@
1
+ $VERBOSE = false
2
+ require 'rubygems'
3
+ require 'fileutils'
4
+ require 'test/unit'
5
+ require 'contest'
6
+
7
+ require File.dirname(__FILE__) + '/../lib/tokyo_model'
8
+
9
+ def tmpdir
10
+ @tmpdir ||= File.join(File.dirname(File.expand_path(__FILE__)), "tmp")
11
+ end
12
+
13
+ def dbpath
14
+ "#{tmpdir}/test.tdb"
15
+ end
16
+
17
+ FileUtils.mkdir_p tmpdir
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubysouth-tokyo_model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Adrian Mugnolo
8
+ - Norman Clarke
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-04-17 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: newgem
18
+ type: :development
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 1.3.0
25
+ version:
26
+ - !ruby/object:Gem::Dependency
27
+ name: hoe
28
+ type: :development
29
+ version_requirement:
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 1.8.0
35
+ version:
36
+ description: ""
37
+ email:
38
+ - adrian@mugnolo.com
39
+ - norman@randomba.org
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files:
45
+ - History.txt
46
+ - Manifest.txt
47
+ - README.rdoc
48
+ files:
49
+ - History.txt
50
+ - Manifest.txt
51
+ - README.rdoc
52
+ - Rakefile
53
+ - lib/tokyo_model.rb
54
+ - lib/tokyo_model/adapters/abstract_adapter.rb
55
+ - lib/tokyo_model/adapters/file.rb
56
+ - lib/tokyo_model/adapters/tyrant.rb
57
+ - lib/tokyo_model/persistable.rb
58
+ - script/console
59
+ - script/destroy
60
+ - script/generate
61
+ - test/contest.rb
62
+ - test/fixtures/post.rb
63
+ - test/test_basic_model.rb
64
+ - test/test_file_adapter.rb
65
+ - test/test_helper.rb
66
+ has_rdoc: true
67
+ homepage: http://github.com/rubysouth/tokyo_model
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --main
71
+ - README.rdoc
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ version:
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: "0"
85
+ version:
86
+ requirements: []
87
+
88
+ rubyforge_project: tokyo-model
89
+ rubygems_version: 1.2.0
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: ""
93
+ test_files:
94
+ - test/test_basic_model.rb
95
+ - test/test_file_adapter.rb
96
+ - test/test_helper.rb