palsy 0.0.1
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.
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +91 -0
- data/Rakefile +38 -0
- data/lib/palsy/basic/collection.rb +46 -0
- data/lib/palsy/basic/generic.rb +89 -0
- data/lib/palsy/basic/list.rb +134 -0
- data/lib/palsy/basic/map.rb +92 -0
- data/lib/palsy/basic/object.rb +64 -0
- data/lib/palsy/basic/set.rb +115 -0
- data/lib/palsy/basic.rb +4 -0
- data/lib/palsy/version.rb +1 -0
- data/lib/palsy.rb +92 -0
- data/palsy.gemspec +26 -0
- data/test/helper.rb +51 -0
- data/test/test_collection.rb +18 -0
- data/test/test_generic.rb +35 -0
- data/test/test_init.rb +41 -0
- data/test/test_list.rb +53 -0
- data/test/test_map.rb +78 -0
- data/test/test_object.rb +40 -0
- data/test/test_set.rb +81 -0
- metadata +158 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Erik Hollensbe
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# Palsy - Set and forget it
|
2
|
+
|
3
|
+
Present ruby core data structures in a manner similar to perl's tie backed by a
|
4
|
+
SQLite database. Intended to be as simple as possible, sacrificing performance
|
5
|
+
and flexibility to do so.
|
6
|
+
|
7
|
+
It is not a 1:1 emulation of tie, as ruby cannot support this. What it does do
|
8
|
+
is provide a convincing enough facsimile by emulating the interface, and making
|
9
|
+
it easy to convert to native ruby types when that's not enough.
|
10
|
+
|
11
|
+
This library is completely unapologetic with regards to how little it does or
|
12
|
+
how slow it does things.
|
13
|
+
|
14
|
+
All writes are fully consistent, which is something SQLite gives us. Reads
|
15
|
+
always hit the database. This allows us to reason more clearly about how our
|
16
|
+
data persists, even in concurrent models where shared state can get very
|
17
|
+
complicated to use.
|
18
|
+
|
19
|
+
This was largely written to deal with problems I had to resolve in
|
20
|
+
[chef-workflow](https://github.com/chef-workflow). If you like the idea but
|
21
|
+
would like something more general or support for your favorite database, check
|
22
|
+
out [Moneta](https://github.com/minad/moneta) which is a little more geared
|
23
|
+
towards having a flexible backend.
|
24
|
+
|
25
|
+
## Usage Example
|
26
|
+
|
27
|
+
Here's an example for dealing with several collections.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'palsy'
|
31
|
+
|
32
|
+
# only one palsy instance exists in a given ruby process
|
33
|
+
Palsy.database = 'test.db'
|
34
|
+
# first arg is table name, second is object name.
|
35
|
+
# standard datatypes are aggressive indexed and constrained. just because we
|
36
|
+
# don't care about performance doesn't mean we ignore easy wins.
|
37
|
+
set_a = Palsy::Set.new('some_sets', 'set_a')
|
38
|
+
set_b = Palsy::Set.new('some_sets', 'set_b')
|
39
|
+
set_c = Palsy::Set.new('some_sets', 'set_c')
|
40
|
+
|
41
|
+
set_a.add('something')
|
42
|
+
set_b.add('something_else')
|
43
|
+
# they are nestable like you'd expect.
|
44
|
+
set_c.add(set_a)
|
45
|
+
|
46
|
+
# enumerable works with collections like you'd expect. the results however are
|
47
|
+
# not palsy objects, so you cannot expect persistence by transformating them.
|
48
|
+
# consequently, most bang methods will not work as you expect either.
|
49
|
+
set_a.group_by(&:length)
|
50
|
+
|
51
|
+
# they are not ruby 'Set' objects, they just act like them.
|
52
|
+
# to get at more esoteric methods you'll need to convert them to the proper
|
53
|
+
# ruby type.
|
54
|
+
#
|
55
|
+
# For example, Set theory operators on Palsy::Set are not first class, you need
|
56
|
+
# to convert to a ruby Set first. Casting to array, hash and set is something
|
57
|
+
# we try to support wherever possible.
|
58
|
+
#
|
59
|
+
# Below we get the union of set_a and set_b
|
60
|
+
|
61
|
+
set_a.to_set | set_b.to_set
|
62
|
+
```
|
63
|
+
|
64
|
+
## Installation
|
65
|
+
|
66
|
+
Add this line to your application's Gemfile:
|
67
|
+
|
68
|
+
gem 'palsy'
|
69
|
+
|
70
|
+
And then execute:
|
71
|
+
|
72
|
+
$ bundle
|
73
|
+
|
74
|
+
Or install it yourself as:
|
75
|
+
|
76
|
+
$ gem install palsy
|
77
|
+
|
78
|
+
## Contributing
|
79
|
+
|
80
|
+
1. Fork it
|
81
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
82
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
83
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
84
|
+
5. Create new Pull Request
|
85
|
+
|
86
|
+
Changes to author, license or version information without prior approval will
|
87
|
+
be rejected regardless of other changes.
|
88
|
+
|
89
|
+
## Author
|
90
|
+
|
91
|
+
Erik Hollensbe <erik+github@hollensbe.org>
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rdoc/task'
|
4
|
+
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs << "test"
|
7
|
+
t.test_files = FileList['test/test_*.rb']
|
8
|
+
t.verbose = true
|
9
|
+
end
|
10
|
+
|
11
|
+
RDoc::Task.new do |rdoc|
|
12
|
+
rdoc.title = "Palsy: a simple sqlite layer for persisting data structures"
|
13
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
14
|
+
rdoc.rdoc_files -= ["lib/palsy/version.rb"]
|
15
|
+
if ENV["RDOC_COVER"]
|
16
|
+
rdoc.options << "-C"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "run tests with coverage report"
|
21
|
+
task "test:coverage" do
|
22
|
+
ENV["COVERAGE"] = "1"
|
23
|
+
Rake::Task["test"].invoke
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "run rdoc with coverage report"
|
27
|
+
task :rdoc_cov do
|
28
|
+
# ugh
|
29
|
+
ENV["RDOC_COVER"] = "1"
|
30
|
+
ruby "-S rake rerdoc"
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Clean up build products"
|
34
|
+
task :clean do
|
35
|
+
%w[html coverage].each do |dir|
|
36
|
+
FileUtils.rm_rf(dir)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'palsy/basic/generic'
|
2
|
+
|
3
|
+
class Palsy
|
4
|
+
#
|
5
|
+
# Base class for Collections. This includes Enumerable and makes the object
|
6
|
+
# name a required value. That's literally all it does.
|
7
|
+
#
|
8
|
+
# For more information on how to use this, see Palsy::Generic. If you want to
|
9
|
+
# exploit the Enumerable mixin, you need to define #each.
|
10
|
+
#
|
11
|
+
# Almost all Palsy::Collection objects are instantiated this way (using
|
12
|
+
# Palsy::Map as an example):
|
13
|
+
#
|
14
|
+
# obj = Palsy::Map.new("some_maps", "a_specific_map")
|
15
|
+
# obj[1] = 2
|
16
|
+
# obj[1] == 2 #=> true
|
17
|
+
#
|
18
|
+
# This will create a table called "some_maps" and all i/o will be directed to
|
19
|
+
# that table with an additional key of "a_specific_map". This allows you to
|
20
|
+
# coordinate multiple maps in a single table.
|
21
|
+
#
|
22
|
+
# Another example:
|
23
|
+
#
|
24
|
+
# obj1 = Palsy::Map.new("some_maps", "a_specific_map")
|
25
|
+
# obj2 = Palsy::Map.new("some_maps", "a_specific_map")
|
26
|
+
# obj3 = Palsy::Map.new("some_maps", "a_different_map")
|
27
|
+
#
|
28
|
+
# obj1 == obj2 #=> true
|
29
|
+
# obj1[1] = 2
|
30
|
+
# obj2[1] == 2 #=> also true
|
31
|
+
# obj3 == obj1 #=> false (different map keys)
|
32
|
+
# obj3[1] = 3
|
33
|
+
# obj2[1] == 3 #=> also false
|
34
|
+
#
|
35
|
+
class Collection < Generic
|
36
|
+
include Enumerable
|
37
|
+
|
38
|
+
#
|
39
|
+
# See the documentation for Palsy::Collection.
|
40
|
+
#
|
41
|
+
def initialize(table_name, object_name)
|
42
|
+
raise "an object_name must be provided!" unless object_name
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'palsy'
|
2
|
+
|
3
|
+
class Palsy
|
4
|
+
#
|
5
|
+
# Palsy::Generic is the base type for Palsy types. It implements the basic
|
6
|
+
# things needed for Palsy to work.
|
7
|
+
#
|
8
|
+
# Creating your own type is just a function of subclassing this and
|
9
|
+
# overriding the method Palsy::Generic#create_table, and adding methods you
|
10
|
+
# expect users to use to operate against the type. For example, this is what
|
11
|
+
# Palsy::Object#create_table looks like:
|
12
|
+
#
|
13
|
+
# class Palsy::Object < Palsy::Generic
|
14
|
+
# def create_table
|
15
|
+
# @db.execute %Q[
|
16
|
+
# create table if not exists #{@table_name} (
|
17
|
+
# id integer not null primary key autoincrement,
|
18
|
+
# key varchar(255) not null,
|
19
|
+
# value text not null,
|
20
|
+
# UNIQUE(key)
|
21
|
+
# )
|
22
|
+
# ]
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
class Generic
|
27
|
+
# The instance of Palsy. Overwriting this is probably not a good idea.
|
28
|
+
attr_accessor :db
|
29
|
+
# The name of the table this object is bound to.
|
30
|
+
attr_reader :table_name
|
31
|
+
# The name of the object this object is bound to inside the table. May be
|
32
|
+
# nil for certain types.
|
33
|
+
attr_reader :object_name
|
34
|
+
|
35
|
+
#
|
36
|
+
# This is the base constructor for all Palsy types and should always be run
|
37
|
+
# in subclasses before any action is taken by the class's initializer.
|
38
|
+
#
|
39
|
+
def initialize(table_name, object_name=nil)
|
40
|
+
raise "a table_name must be provided!" unless table_name
|
41
|
+
|
42
|
+
@table_name = table_name
|
43
|
+
@object_name = object_name
|
44
|
+
post_marshal_init
|
45
|
+
create_table
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Marshal helper to load objects.
|
50
|
+
#
|
51
|
+
def self._load(value)
|
52
|
+
obj = self.new(*Marshal.load(value))
|
53
|
+
return obj
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Marshal helper to dump objects. Only the table and object names are
|
58
|
+
# preserved.
|
59
|
+
#
|
60
|
+
def _dump(level)
|
61
|
+
self.db = nil
|
62
|
+
res = Marshal.dump([@table_name, @object_name])
|
63
|
+
post_marshal_init
|
64
|
+
return res
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Helper to manage the database instance for Marshal operations.
|
69
|
+
#
|
70
|
+
def post_marshal_init
|
71
|
+
@db = Palsy.instance
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Virtual method to define the create_table interface. Just raises,
|
76
|
+
# intended to be overridden.
|
77
|
+
#
|
78
|
+
def create_table
|
79
|
+
raise "Do not use the Generic type directly!"
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Equality method for comparing Palsy objects.
|
84
|
+
#
|
85
|
+
def ==(other)
|
86
|
+
[:db, :table_name, :object_name].all? { |x| self.send(x) == other.send(x) }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'palsy/basic/collection'
|
2
|
+
|
3
|
+
class Palsy
|
4
|
+
#
|
5
|
+
# Palsy::List is a kind of Palsy::Collection and implements List semantics
|
6
|
+
# similar to Ruby's Array class. For more random-access behavior that Array
|
7
|
+
# provides, convert to a Ruby array with Palsy::List#to_a.
|
8
|
+
#
|
9
|
+
# All values of the list must be capable of being Marshalled.
|
10
|
+
#
|
11
|
+
# Here's an example:
|
12
|
+
#
|
13
|
+
# obj = Palsy::List.new("some_lists", "a_specific_list")
|
14
|
+
# obj.push 2
|
15
|
+
# obj.pop == 2 #=> true
|
16
|
+
#
|
17
|
+
# This will create a table called "some_lists" and all i/o will be directed to
|
18
|
+
# that table with an additional key of "a_specific_list". This allows you to
|
19
|
+
# coordinate multiple lists in a single table.
|
20
|
+
#
|
21
|
+
# Another example:
|
22
|
+
#
|
23
|
+
# obj1 = Palsy::List.new("some_lists", "a_specific_list")
|
24
|
+
# obj2 = Palsy::List.new("some_lists", "a_specific_list")
|
25
|
+
# obj3 = Palsy::List.new("some_lists", "a_different_list")
|
26
|
+
#
|
27
|
+
# obj1 == obj2 #=> true
|
28
|
+
# obj1.push 2
|
29
|
+
# obj2.pop == 2 #=> also true
|
30
|
+
# obj3 == obj1 #=> false (different list keys)
|
31
|
+
# obj.push 2
|
32
|
+
# obj3.push 3
|
33
|
+
# obj2.pop == 3 #=> also false
|
34
|
+
#
|
35
|
+
class List < Collection
|
36
|
+
#
|
37
|
+
# Add a value to the tail of the list.
|
38
|
+
#
|
39
|
+
def push(val)
|
40
|
+
@db.execute(
|
41
|
+
"insert into #{@table_name} (name, value) values (?, ?)",
|
42
|
+
[@object_name, Marshal.dump(val)]
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
alias << push
|
47
|
+
|
48
|
+
#
|
49
|
+
# Add a value to the head of the list.
|
50
|
+
#
|
51
|
+
def unshift(val)
|
52
|
+
replace([val] + to_a)
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Helper method for mutators.
|
57
|
+
#
|
58
|
+
def mutate(meth)
|
59
|
+
a = to_a
|
60
|
+
val = a.send(meth)
|
61
|
+
replace(a)
|
62
|
+
return val
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Returns the head of the list. Destructive.
|
67
|
+
#
|
68
|
+
def shift
|
69
|
+
mutate(:shift)
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Returns the tail of the list. Destructive.
|
74
|
+
#
|
75
|
+
def pop
|
76
|
+
mutate(:pop)
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Replace the list with the argument, which should be something that acts
|
81
|
+
# like an Enumerable.
|
82
|
+
#
|
83
|
+
def replace(ary)
|
84
|
+
clear
|
85
|
+
|
86
|
+
value_string = ("(?, ?)," * ary.count).chop
|
87
|
+
|
88
|
+
@db.execute(
|
89
|
+
"insert into #{@table_name} (name, value) values #{value_string}",
|
90
|
+
ary.map { |x| [@object_name, Marshal.dump(x)] }.flatten
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Empty the list.
|
96
|
+
#
|
97
|
+
def clear
|
98
|
+
@db.execute("delete from #{@table_name} where name=?", [@object_name])
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Yield each item in the list successively. Note that changes made to the
|
103
|
+
# items yielded will not persist, unless they are Palsy types themselves.
|
104
|
+
#
|
105
|
+
def each
|
106
|
+
to_a.each { |x| yield x }
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Convert the list to a Ruby Array.
|
111
|
+
#
|
112
|
+
def to_a
|
113
|
+
@db.execute(
|
114
|
+
"select value from #{@table_name} where name=? order by id",
|
115
|
+
[@object_name]
|
116
|
+
).map { |x| Marshal.load(x.first) }
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Define the schema for this type.
|
121
|
+
#
|
122
|
+
def create_table
|
123
|
+
@db.execute <<-EOF
|
124
|
+
create table if not exists #{@table_name} (
|
125
|
+
id integer not null primary key autoincrement,
|
126
|
+
name varchar(255) not null,
|
127
|
+
value text not null
|
128
|
+
)
|
129
|
+
EOF
|
130
|
+
|
131
|
+
@db.execute "create index if not exists #{@table_name}_name_index on #{@table_name} (name)"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'palsy/basic/set'
|
2
|
+
|
3
|
+
class Palsy
|
4
|
+
#
|
5
|
+
# Palsy::Map is an emulation of Ruby's Hash class, where objects are
|
6
|
+
# unordered and unique by the keyed value. They are a Palsy::Set and
|
7
|
+
# Palsy::Collection and have Enumerable support. Take a look at Palsy::Object
|
8
|
+
# which is similar but semantically different in a few ways.
|
9
|
+
#
|
10
|
+
# Palsy::Map keys and values must be marshallable, and their uniqueness for
|
11
|
+
# sanity's sake must also be enforced in their marshalled output.
|
12
|
+
#
|
13
|
+
# Here's an example:
|
14
|
+
#
|
15
|
+
# obj = Palsy::Map.new("some_maps", "a_specific_map")
|
16
|
+
# obj[1] = 2
|
17
|
+
# obj[1] == 2 #=> true
|
18
|
+
#
|
19
|
+
# This will create a table called "some_maps" and all i/o will be directed to
|
20
|
+
# that table with an additional key of "a_specific_map". This allows you to
|
21
|
+
# coordinate multiple maps in a single table.
|
22
|
+
#
|
23
|
+
# Another example:
|
24
|
+
#
|
25
|
+
# obj1 = Palsy::Map.new("some_maps", "a_specific_map")
|
26
|
+
# obj2 = Palsy::Map.new("some_maps", "a_specific_map")
|
27
|
+
# obj3 = Palsy::Map.new("some_maps", "a_different_map")
|
28
|
+
#
|
29
|
+
# obj1 == obj2 #=> true
|
30
|
+
# obj1[1] = 2
|
31
|
+
# obj2[1] == 2 #=> also true
|
32
|
+
# obj3 == obj1 #=> false (different map keys)
|
33
|
+
# obj3[1] = 3
|
34
|
+
# obj2[1] == 3 #=> also false
|
35
|
+
#
|
36
|
+
class Map < Set
|
37
|
+
|
38
|
+
#
|
39
|
+
# Obtain the value for a given key. Returns nil if the key does not have an
|
40
|
+
# entry.
|
41
|
+
#
|
42
|
+
def [](key)
|
43
|
+
value = @db.execute("select value from #{@table_name} where name=? and key=?", [@object_name, Marshal.dump(key)]).first.first rescue nil
|
44
|
+
return value && Marshal.load(value)
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Associate a value with a given key. Keys will be deleted from the
|
49
|
+
# database before attempting to write the new pair.
|
50
|
+
#
|
51
|
+
def []=(key, value)
|
52
|
+
delete(key)
|
53
|
+
@db.execute("insert into #{@table_name} (name, key, value) values (?, ?, ?)", [@object_name, Marshal.dump(key), Marshal.dump(value)])
|
54
|
+
value
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Successively yields key and value for each item in the map. Modifications
|
59
|
+
# to either the key or value yielded will not persist.
|
60
|
+
#
|
61
|
+
def each
|
62
|
+
keys.each do |key|
|
63
|
+
yield key, self[key]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Convert this map to a Ruby Hash object.
|
69
|
+
#
|
70
|
+
def to_hash
|
71
|
+
rows = @db.execute("select key, value from #{@table_name} where name=?", [@object_name])
|
72
|
+
Hash[rows.map { |x| x.map { |y| Marshal.load(y) } }]
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# Defines the schema for Maps.
|
77
|
+
#
|
78
|
+
def create_table
|
79
|
+
@db.execute <<-EOF
|
80
|
+
create table if not exists #{@table_name} (
|
81
|
+
id integer not null primary key autoincrement,
|
82
|
+
name varchar(255) not null,
|
83
|
+
key varchar(255) not null,
|
84
|
+
value text not null,
|
85
|
+
UNIQUE(name, key)
|
86
|
+
)
|
87
|
+
EOF
|
88
|
+
|
89
|
+
@db.execute "create index if not exists #{@table_name}_name_idx on #{@table_name} (name)"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'palsy/basic/generic'
|
2
|
+
|
3
|
+
class Palsy
|
4
|
+
#
|
5
|
+
# Basic "symbol table"-ish object.
|
6
|
+
#
|
7
|
+
# The difference between using this over Palsy::Map is that objects take a
|
8
|
+
# full database table, treat all indexes as strings, and do not act like
|
9
|
+
# Palsy::Collection subclasses.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# obj = Palsy::Object.new("object_table")
|
14
|
+
# obj["var1"] = 1
|
15
|
+
# obj["var2"] = 2
|
16
|
+
# obj["var1"] == 1 #=> true
|
17
|
+
#
|
18
|
+
# This will create a table in the database named "object_table" and all i/o
|
19
|
+
# against this object (unless specified otherwise on a per-method basis) will
|
20
|
+
# go through it.
|
21
|
+
#
|
22
|
+
# Note that while all object keys are treated as strings, values are
|
23
|
+
# marshalled and must be capable of doing so.
|
24
|
+
#
|
25
|
+
class Object < Generic
|
26
|
+
#
|
27
|
+
# Get an object by referencing its key. Returns nil unless it exists.
|
28
|
+
#
|
29
|
+
def [](key)
|
30
|
+
value = @db.execute("select value from #{@table_name} where key=?", [key]).first.first rescue nil
|
31
|
+
return value && Marshal.load(value)
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Set an object. Returns the object. The object must be able to be Marshalled.
|
36
|
+
#
|
37
|
+
def []=(key, value)
|
38
|
+
delete(key)
|
39
|
+
@db.execute("insert into #{@table_name} (key, value) values (?, ?)", [key, Marshal.dump(value)])
|
40
|
+
value
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Deletes an object.
|
45
|
+
#
|
46
|
+
def delete(key)
|
47
|
+
@db.execute("delete from #{@table_name} where key=?", [key])
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Defines the schema for this data type.
|
52
|
+
#
|
53
|
+
def create_table
|
54
|
+
@db.execute <<-EOF
|
55
|
+
create table if not exists #{@table_name} (
|
56
|
+
id integer not null primary key autoincrement,
|
57
|
+
key varchar(255) not null,
|
58
|
+
value text not null,
|
59
|
+
UNIQUE(key)
|
60
|
+
)
|
61
|
+
EOF
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|