rubysouth-tokyo_model 0.0.2 → 0.0.4

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.
@@ -1,3 +1,13 @@
1
+ == 0.0.4 2009-04-21
2
+
3
+ * 1 major enhancement:
4
+ * Connection over TokyoTyrant now working
5
+
6
+ == 0.0.3 2009-04-20
7
+
8
+ * 1 major enhancement:
9
+ * Added beginning of query support.
10
+
1
11
  == 0.0.2 2009-04-17
2
12
 
3
13
  * 1 major enhancement:
@@ -7,6 +7,7 @@ lib/tokyo_model/adapters/abstract_adapter.rb
7
7
  lib/tokyo_model/adapters/file.rb
8
8
  lib/tokyo_model/adapters/tyrant.rb
9
9
  lib/tokyo_model/persistable.rb
10
+ lib/tokyo_model/query.rb
10
11
  script/console
11
12
  script/destroy
12
13
  script/generate
@@ -15,4 +16,5 @@ test/fixtures/post.rb
15
16
  test/test_basic_model.rb
16
17
  test/test_file_adapter.rb
17
18
  test/test_helper.rb
18
- tokyo_model.gemspec
19
+ test/test_query.rb
20
+ tokyo_model.gemspec
@@ -1,8 +1,8 @@
1
1
  = TokyoModel
2
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.
3
+ TokyoModel is a small, lightweight ORM that uses TokyoCabinet's table
4
+ databases (TDB) as a storage backend. It is to ActiveRecord or Sequel what
5
+ Sinatra is to Rails or Merb.
6
6
 
7
7
  Our first public release is planned for some time in late April 2009. At the
8
8
  moment almost ALMOST NOTHING is implemented. It wouldn't even be fair to
@@ -12,21 +12,20 @@ describe this as pre-alpha. DO NOT USE THIS.
12
12
 
13
13
  Copyright (c) 2009 Adrian Mugnolo and Norman Clarke
14
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:
15
+ Permission is hereby granted, free of charge, to any person obtaining a copy
16
+ of this software and associated documentation files (the 'Software'), to deal
17
+ in the Software without restriction, including without limitation the rights
18
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19
+ copies of the Software, and to permit persons to whom the Software is
20
+ furnished to do so, subject to the following conditions:
22
21
 
23
- The above copyright notice and this permission notice shall be
24
- included in all copies or substantial portions of the Software.
22
+ The above copyright notice and this permission notice shall be included in all
23
+ copies or substantial portions of the Software.
25
24
 
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.
25
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
+ SOFTWARE.
@@ -3,10 +3,11 @@ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) ||
3
3
  require 'uri'
4
4
  require 'tokyo_model/persistable'
5
5
  require 'tokyo_model/adapters/abstract_adapter'
6
+ require 'tokyo_model/query'
6
7
 
7
8
  module TokyoModel
8
9
 
9
- VERSION = '0.0.2'
10
+ VERSION = '0.0.4'
10
11
  ADAPTERS = [:file, :tyrant].freeze
11
12
  DATABASES = []
12
13
 
@@ -26,6 +27,10 @@ module TokyoModel
26
27
  DATABASES.each { |d| d.close }
27
28
  end
28
29
 
30
+ def self.query
31
+ Query.new(DATABASES.first)
32
+ end
33
+
29
34
  private
30
35
 
31
36
  def self.adapter(scheme)
@@ -1,13 +1,19 @@
1
1
  module TokyoModel
2
2
  module Adapters
3
3
 
4
- # This class exists almost exclusively to indicate the methods that all
4
+ class ConnectionError < StandardError ; end
5
+
6
+ # This class exists almost exclusively to document the methods that all
5
7
  # adapters must implement. The default implementation simply delegates
6
8
  # everything to the underlying database file or connection.
7
9
  class AbstractAdapter
8
10
 
9
11
  attr :db
10
12
 
13
+ def connection
14
+ @db
15
+ end
16
+
11
17
  # Delete the record identified by +pkey+.
12
18
  def out(pkey)
13
19
  @db.out(pkey)
@@ -30,11 +36,11 @@ module TokyoModel
30
36
  end
31
37
 
32
38
  # Perform a query.
33
- def query(*args)
39
+ def query
34
40
  raise NotImplementedError.new
35
41
  end
36
42
 
37
- # Delegate any unknown method calls to the underlying @db.
43
+ # Delegate any unknown method calls to the underlying +@db+.
38
44
  def method_missing(symbol, *args)
39
45
  @db.send(symbol, *args)
40
46
  end
@@ -6,7 +6,6 @@ module TokyoModel
6
6
  # This adapter provides access to TokyoCabinet TDB files.
7
7
  class File < AbstractAdapter
8
8
 
9
-
10
9
  # Permitted open modes for accessing TDB files: +:read+, +:write+,
11
10
  # +:create+, +:truncate+, +:nolock+, +:lock_noblock+ and +:sync+
12
11
  OPEN_MODES = {
@@ -28,6 +27,10 @@ module TokyoModel
28
27
  @db.open(uri.path, args.empty? ? File.default_open_mode : File.open_mode(*args))
29
28
  end
30
29
 
30
+ def query
31
+ Query.new(TokyoCabinet::TDBQRY.new(@db))
32
+ end
33
+
31
34
  class << self
32
35
 
33
36
  # The default open mode is read/write.
@@ -16,12 +16,16 @@ module TokyoModel
16
16
  def initialize(uri, *args)
17
17
  @db = TokyoTyrant::RDBTBL::new
18
18
  if uri.scheme == "unix"
19
- @db.open(uri.path, 0)
19
+ @db.open(uri.path, 0) || raise(ConnectionError.new(@db.errmsg(@db.ecode)))
20
20
  else
21
- @db.open(uri.host, uri.port || DEFAULT_PORT)
21
+ @db.open(uri.host, uri.port || DEFAULT_PORT) || raise(ConnectionError.new(@db.errmsg(@db.ecode)))
22
22
  end
23
23
  end
24
24
 
25
+ def query
26
+ Query.new(TokyoTyrant::RDBQRY.new(@db))
27
+ end
28
+
25
29
  end
26
30
  end
27
31
  end
@@ -8,8 +8,13 @@ module TokyoModel
8
8
 
9
9
  module ClassMethods
10
10
 
11
- def connect(uri, *args)
12
- @db = TokyoModel.open(uri, *args)
11
+ def db=(*args)
12
+ db_or_uri = args.shift
13
+ if db_or_uri.respond_to?(:scheme)
14
+ @db = TokyoModel.open(db_or_uri, *args)
15
+ else
16
+ @db = db_or_uri
17
+ end
13
18
  end
14
19
 
15
20
  def db
@@ -19,7 +24,7 @@ module TokyoModel
19
24
  def get(id)
20
25
  obj = new
21
26
  if record = db.get(id)
22
- record.each { |k, v| obj.send("#{k}=".to_sym, v) }
27
+ record.each { |k, v| obj.send("#{k}=".to_sym, v) if obj.respond_to?("#{k}=".to_sym) }
23
28
  obj.id = id
24
29
  end
25
30
  obj
@@ -27,7 +32,21 @@ module TokyoModel
27
32
  alias_method :find, :get
28
33
 
29
34
  def setter_methods
30
- instance_methods.select {|m| m =~ /[^=]$/ && instance_methods.include?("#{m}=") && !%w(taguri).include?(m) }
35
+ if !@setter_methods
36
+ im = instance_methods - Object.instance_methods
37
+ @setter_methods = im.select {|m| im.include?("#{m}=") && m =~ /[^=]$/ }
38
+ end
39
+ @setter_methods
40
+ end
41
+
42
+ def find(&block)
43
+ ids = query.conditions(&block).execute
44
+ ids.inject([]) { |m, o| m << get(o); m }
45
+ end
46
+
47
+ def query
48
+ type = self.to_s
49
+ db.query.conditions { type_is type }
31
50
  end
32
51
 
33
52
  end
@@ -37,11 +56,12 @@ module TokyoModel
37
56
  end
38
57
 
39
58
  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
59
+ hash = {}
60
+ self.class.setter_methods.each do |s|
61
+ val = send(s.to_sym)
62
+ hash[s] = val if val
44
63
  end
64
+ hash
45
65
  end
46
66
 
47
67
  def db
@@ -57,7 +77,7 @@ module TokyoModel
57
77
  end
58
78
 
59
79
  def put
60
- db.put(id, attributes)
80
+ db.put(id, attributes.merge({ "type" => self.class.to_s }))
61
81
  end
62
82
  alias_method :save, :put
63
83
 
@@ -0,0 +1,109 @@
1
+ module TokyoModel
2
+
3
+ class Query
4
+
5
+ attr :conditions, :db_query
6
+
7
+ # Query condition: string is equal to
8
+ QCSTREQ = 1
9
+ # Query condition: string is included in
10
+ QCSTRINC = 2
11
+ # Query condition: string begins with
12
+ QCSTRBW = 3
13
+ # Query condition: string ends with
14
+ QCSTREW = 4
15
+ # Query condition: string includes all tokens in
16
+ QCSTRAND = 5
17
+ # Query condition: string includes at least one token in
18
+ QCSTROR = 6
19
+ # Query condition: string is equal to at least one token in
20
+ QCSTROREQ = 7
21
+ # Query condition: string matches regular expressions of
22
+ QCSTRRX = 8
23
+ # Query condition: number is equal to
24
+ QCNUMEQ = 9
25
+ # Query condition: number is greater than
26
+ QCNUMGT = 10
27
+ # Query condition: number is greater than or equal to
28
+ QCNUMGE = 11
29
+ # Query condition: number is less than
30
+ QCNUMLT = 12
31
+ # Query condition: number is less than or equal to
32
+ QCNUMLE = 13
33
+ # Query condition: number is between two tokens of
34
+ QCNUMBT = 14
35
+ # Query condition: number is equal to at least one token in
36
+ QCNUMOREQ = 15
37
+ # Query condition: negation flag
38
+ QCNEGATE = 1 << 24
39
+ # Query condition: no index flag
40
+ QCNOIDX = 1 << 25
41
+ # Order type: string ascending
42
+ QOSTRASC = 1
43
+ # Order type: string descending
44
+ QOSTRDESC = 2
45
+ # Order type: number ascending
46
+ QONUMASC = 3
47
+ # Order type: number descending
48
+ QONUMDESC = 4
49
+ # Post treatment: modify the record
50
+ QPPUT = 1 << 0
51
+ # Post treatment: remove the record
52
+ QPOUT = 1 << 1
53
+ # Post treatment: stop the iteration
54
+ QPSTOP = 1 << 24
55
+
56
+ def initialize(db_query)
57
+ @db_query = db_query
58
+ @conditions = []
59
+ end
60
+
61
+ def conditions(&block)
62
+ @conditions += QueryConditions.new.instance_eval(&block)
63
+ self
64
+ end
65
+
66
+ def execute
67
+ @conditions.each { |c| @db_query.addcond(*c) }
68
+ @db_query.search
69
+ end
70
+
71
+ CONDITIONS = {
72
+ :is => QCSTREQ,
73
+ :like => QCSTRINC,
74
+ :has => QCSTROR,
75
+ :has_all => QCSTRAND,
76
+ :equals_one => QCSTROREQ,
77
+ :begins_with => QCSTRBW,
78
+ :ends_with => QCSTREW,
79
+ :matches => QCSTRRX,
80
+ :eq => QCNUMEQ,
81
+ :gt => QCNUMGT,
82
+ :gte => QCNUMGE,
83
+ :lt => QCNUMLT,
84
+ :lte => QCNUMLE,
85
+ :btw => QCNUMBT,
86
+ :in => QCNUMOREQ
87
+ }
88
+ end
89
+
90
+ class QueryConditions
91
+
92
+ attr :conditions
93
+
94
+ METHODS = Query::CONDITIONS.keys.inject([]) { |m, o| m << o.to_s }.join("|")
95
+
96
+ def initialize
97
+ @conditions = []
98
+ end
99
+
100
+ def method_missing(symbol, *args)
101
+ symbol.to_s =~ /([a-z0-9_]+)_(#{METHODS}+)(!)?/
102
+ name, op, negate = $1, $2.to_sym, $3
103
+ op = negate ? Query::QCNEGATE | Query::CONDITIONS[op] : Query::CONDITIONS[op]
104
+ conditions << [name, op] + args
105
+ end
106
+
107
+ end
108
+
109
+ end
@@ -1,27 +1,15 @@
1
1
  require 'test_helper'
2
- require File.dirname(__FILE__) + "/fixtures/post.rb"
3
2
 
4
3
  class TestBasicModel < Test::Unit::TestCase
5
4
 
6
5
  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
6
+ load_fixtures
19
7
  end
20
8
 
21
9
  context "a model" do
22
10
 
23
11
  should "have a db connection" do
24
- assert_not_nil Post.db
12
+ assert Post.db.respond_to?(:get)
25
13
  end
26
14
 
27
15
  should "be gettable" do
@@ -5,13 +5,25 @@ require 'test/unit'
5
5
  require 'contest'
6
6
 
7
7
  require File.dirname(__FILE__) + '/../lib/tokyo_model'
8
+ require File.dirname(__FILE__) + "/fixtures/post.rb"
8
9
 
9
10
  def tmpdir
10
11
  @tmpdir ||= File.join(File.dirname(File.expand_path(__FILE__)), "tmp")
11
12
  end
12
13
 
13
14
  def dbpath
14
- "#{tmpdir}/test.tdb"
15
+ "#{tmpdir}/test.tct"
15
16
  end
16
17
 
17
- FileUtils.mkdir_p tmpdir
18
+ def load_fixtures
19
+ @post = Post.new
20
+ @post.title = "First post!"
21
+ @post.body = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"
22
+ @post.author = "John Doe"
23
+ @post.permalink = "http://example.org/posts/1"
24
+ @post.save
25
+ end
26
+
27
+ FileUtils.mkdir_p tmpdir
28
+ TokyoModel.open("file:#{dbpath}", :write, :read, :create, :truncate)
29
+ # TokyoModel.open("tyrant://127.0.0.1")
@@ -0,0 +1,18 @@
1
+ require 'test_helper'
2
+
3
+ class TestQuery < Test::Unit::TestCase
4
+
5
+ def setup
6
+ load_fixtures
7
+ end
8
+
9
+ def test_a_query
10
+ @posts = Post.find do
11
+ title_has! "NONE!"
12
+ author_is "John Doe"
13
+ body_matches "[a-zA-Z,\.\s]*$"
14
+ end
15
+ p @posts
16
+ end
17
+
18
+ end
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{tokyo_model}
5
+ s.version = "0.0.4"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Adrian Mugnolo", "Norman Clarke"]
9
+ s.date = %q{2009-04-21}
10
+ s.description = %q{}
11
+ s.email = ["adrian@mugnolo.com", "norman@randomba.org"]
12
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.rdoc"]
13
+ s.files = ["History.txt", "Manifest.txt", "README.rdoc", "Rakefile", "lib/tokyo_model.rb", "lib/tokyo_model/adapters/abstract_adapter.rb", "lib/tokyo_model/adapters/file.rb", "lib/tokyo_model/adapters/tyrant.rb", "lib/tokyo_model/persistable.rb", "lib/tokyo_model/query.rb", "script/console", "script/destroy", "script/generate", "test/contest.rb", "test/fixtures/post.rb", "test/test_basic_model.rb", "test/test_file_adapter.rb", "test/test_helper.rb", "test/test_query.rb", "tokyo_model.gemspec"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/rubysouth/tokyo_model}
16
+ s.rdoc_options = ["--main", "README.rdoc"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{tokyo-model}
19
+ s.rubygems_version = %q{1.3.2}
20
+ s.summary = %q{}
21
+ s.test_files = ["test/test_basic_model.rb", "test/test_file_adapter.rb", "test/test_helper.rb", "test/test_query.rb"]
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 3
26
+
27
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
+ s.add_development_dependency(%q<newgem>, [">= 1.3.0"])
29
+ s.add_development_dependency(%q<hoe>, [">= 1.8.0"])
30
+ else
31
+ s.add_dependency(%q<newgem>, [">= 1.3.0"])
32
+ s.add_dependency(%q<hoe>, [">= 1.8.0"])
33
+ end
34
+ else
35
+ s.add_dependency(%q<newgem>, [">= 1.3.0"])
36
+ s.add_dependency(%q<hoe>, [">= 1.8.0"])
37
+ end
38
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubysouth-tokyo_model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Mugnolo
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-04-17 00:00:00 -07:00
13
+ date: 2009-04-21 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -55,6 +55,7 @@ files:
55
55
  - lib/tokyo_model/adapters/file.rb
56
56
  - lib/tokyo_model/adapters/tyrant.rb
57
57
  - lib/tokyo_model/persistable.rb
58
+ - lib/tokyo_model/query.rb
58
59
  - script/console
59
60
  - script/destroy
60
61
  - script/generate
@@ -63,6 +64,8 @@ files:
63
64
  - test/test_basic_model.rb
64
65
  - test/test_file_adapter.rb
65
66
  - test/test_helper.rb
67
+ - test/test_query.rb
68
+ - tokyo_model.gemspec
66
69
  has_rdoc: true
67
70
  homepage: http://github.com/rubysouth/tokyo_model
68
71
  post_install_message:
@@ -94,3 +97,4 @@ test_files:
94
97
  - test/test_basic_model.rb
95
98
  - test/test_file_adapter.rb
96
99
  - test/test_helper.rb
100
+ - test/test_query.rb