rubysouth-tokyo_model 0.0.2 → 0.0.4

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