mudnaes-dbstruct 0.1.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/README +56 -0
- data/dbstruct.gemspec +21 -0
- data/lib/dbstruct.rb +204 -0
- data/spec/dbstruct_spec.rb +143 -0
- metadata +82 -0
data/README
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
This is a very simple framework to be able to access database rows as
|
2
|
+
objects without forcing you to inherit from a Model class. Instead
|
3
|
+
you create the class-hierarchy you want and use mixin to add
|
4
|
+
persistance functionality to the object.
|
5
|
+
|
6
|
+
It a lot simpler than an real Object-Relation-Mapper since it's ignores relations.
|
7
|
+
|
8
|
+
The benefit of this framework is that you can write code like this:
|
9
|
+
|
10
|
+
class AnObject
|
11
|
+
include DBStruct
|
12
|
+
@non_persisted_field
|
13
|
+
attr_accessor :non_persisted_field
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
@non_persisted_field = args[0]
|
17
|
+
end
|
18
|
+
|
19
|
+
def return_value_from_field
|
20
|
+
return @non_persisted_field
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Create a simple migration:
|
25
|
+
|
26
|
+
class CreateDb < Sequel::Migration
|
27
|
+
def up
|
28
|
+
create_table :person do
|
29
|
+
primary_key :id
|
30
|
+
text :name
|
31
|
+
float :amount
|
32
|
+
integer :age
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
An then start coding. To create new field you only have to add it to the migration.
|
38
|
+
|
39
|
+
DB = Sequel.sqlite '', :logger => [Logger.new($stdout)]
|
40
|
+
CreateDb.apply(DB,:up)
|
41
|
+
AnObject.bind_table(DB,:person)
|
42
|
+
|
43
|
+
r = AnObject.template
|
44
|
+
r.name = 'Donald'
|
45
|
+
r.amount = 10.1
|
46
|
+
r.age = 77
|
47
|
+
|
48
|
+
r.insert(DB)
|
49
|
+
|
50
|
+
r.age = 98
|
51
|
+
r.update(DB)
|
52
|
+
r.delete(DB)
|
53
|
+
|
54
|
+
This framework relies heavely on the work of others (Sequel and OpenStruct)
|
55
|
+
|
56
|
+
Morten Udnæs.
|
data/dbstruct.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "dbstruct"
|
3
|
+
s.version = "0.1.1"
|
4
|
+
s.date = "2008-12-03"
|
5
|
+
s.summary = "Easy to use library for mapping rows between objects and database"
|
6
|
+
s.email = "mudnaes@gmail.com"
|
7
|
+
s.homepage = "http://github.com/mudnaes/dbstruct/tree/master"
|
8
|
+
s.description = "This is a very simple framework to be able to access database rows as objects without forcing you to inherit from a Model class. Instead you create the class-hierarchy you want and use mixin to add persistance functionality to the object."
|
9
|
+
s.has_rdoc = true
|
10
|
+
s.authors = ["Morten Udnæs"]
|
11
|
+
s.files = ["README",
|
12
|
+
"dbstruct.gemspec",
|
13
|
+
"lib/dbstruct.rb"]
|
14
|
+
s.test_files = ["spec/dbstruct_spec.rb"]
|
15
|
+
s.rdoc_options = ["--main", "README.txt"]
|
16
|
+
s.extra_rdoc_files = ["README"]
|
17
|
+
s.add_dependency("diff-lcs", ["> 0.0.0"])
|
18
|
+
s.add_dependency("mime-types", ["> 0.0.0"])
|
19
|
+
s.add_dependency("open4", ["> 0.0.0"])
|
20
|
+
end
|
21
|
+
|
data/lib/dbstruct.rb
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module DBStruct
|
5
|
+
# The purpose of this module is to have a struct inside an ordinary object
|
6
|
+
# to contain feilds for persistence. The fields are accessed the same way
|
7
|
+
# as the other fields inside the object..
|
8
|
+
#
|
9
|
+
|
10
|
+
|
11
|
+
# Define dynamic accessors to struct fields
|
12
|
+
def method_missing(mid, *args)
|
13
|
+
fieldname = mid.id2name
|
14
|
+
|
15
|
+
if mid.id2name.match("=")
|
16
|
+
fieldname.chop!
|
17
|
+
if args.length != 1
|
18
|
+
raise ArgumentError, "Setter must have only one argument. Number of arguments found was (#{len} for 1)", caller(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Make sure fields aren't added in error (typos)
|
22
|
+
if !@_dbdata.marshal_dump.key?(fieldname.to_sym) & @_frozen
|
23
|
+
raise ArgumentError, "Struct fields are frozen, reopen before using <field>= . Field was (#{fieldname} for 1)", caller(1)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if @_dbdata == nil
|
28
|
+
super(mid, *args)
|
29
|
+
|
30
|
+
elsif @_dbdata.marshal_dump.key?(fieldname.to_sym)
|
31
|
+
@_dbdata.send(mid.id2name,args[0])
|
32
|
+
|
33
|
+
# Route method missing to struct object
|
34
|
+
elsif !@_frozen & mid.id2name.match("=")
|
35
|
+
@_dbdata.method_missing(mid, *args)
|
36
|
+
|
37
|
+
# Forward methods missing not related to struct
|
38
|
+
else
|
39
|
+
super(mid, *args)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Used for creating an object based on an hash (primarily for loading records directly from DB
|
44
|
+
def load(response)
|
45
|
+
if response != nil
|
46
|
+
@_dbdata = OpenStruct.new(response)
|
47
|
+
@_frozen = true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Export hash from struct
|
52
|
+
def save(*args)
|
53
|
+
@_dbdata.marshal_dump(*args)
|
54
|
+
end
|
55
|
+
|
56
|
+
def get(db, pkid)
|
57
|
+
@_dbdata.pkid = db[self.class.get_table].filter(:pkid =>pkid)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def insert(db)
|
62
|
+
@_dbdata.pkid = db[self.class.get_table].insert(save)
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete(db,search_criteria)
|
66
|
+
db[self.class.get_table].filter({:pkid =>pkid}).delete
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def update(db)
|
71
|
+
db[self.class.get_table].filter({:pkid => pkid}).update(save)
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
# Define which fields are needed. Sent in as an array of symbols or strings
|
76
|
+
def setup_fields(fields)
|
77
|
+
h = {}
|
78
|
+
fields.each do |field|
|
79
|
+
if field.kind_of? Symbol
|
80
|
+
h[field.to_s] = nil
|
81
|
+
else
|
82
|
+
h[field] = nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
@_dbdata = OpenStruct.new(fields)
|
86
|
+
@_frozen = true
|
87
|
+
end
|
88
|
+
|
89
|
+
# Avoid anyone referencing @_dbdata directly
|
90
|
+
def empty?
|
91
|
+
@_dbdata == nil
|
92
|
+
end
|
93
|
+
|
94
|
+
# When overiding method_missing this one should also be...
|
95
|
+
def respond_to(*args)
|
96
|
+
super(*args)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Freeze struct so new fields aren't added by mistake
|
100
|
+
def freeze_fields
|
101
|
+
@_frozen = true
|
102
|
+
end
|
103
|
+
|
104
|
+
# Unfreeze struct so new fields can be added manually. Remember to freeze afterwards
|
105
|
+
def unfreeze_fields
|
106
|
+
@_frozen = false
|
107
|
+
end
|
108
|
+
|
109
|
+
#Class methods for included dbstructs
|
110
|
+
def self.included receiver
|
111
|
+
receiver.extend DBStructClassMethods
|
112
|
+
end
|
113
|
+
|
114
|
+
module DBStructClassMethods
|
115
|
+
# Used to create objects based on database rows
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
def create(rows)
|
120
|
+
list = []
|
121
|
+
if rows == nil
|
122
|
+
return list
|
123
|
+
end
|
124
|
+
rows.each do |row|
|
125
|
+
the_instance = self.new
|
126
|
+
the_instance.load(row)
|
127
|
+
list << the_instance
|
128
|
+
end
|
129
|
+
list
|
130
|
+
end
|
131
|
+
|
132
|
+
#Hack enable referencing modularized class-variable from instance
|
133
|
+
def get_table
|
134
|
+
return @@_table
|
135
|
+
end
|
136
|
+
|
137
|
+
#Binds class to given table in the database, Sequel specific
|
138
|
+
def bind_table(db,table)
|
139
|
+
tablefields = []
|
140
|
+
@@_table = table
|
141
|
+
|
142
|
+
if db.schema(table).empty?
|
143
|
+
raise ArgumentError, "Table [#{table}] was not found. Unable to bind object", caller(1)
|
144
|
+
end
|
145
|
+
|
146
|
+
db.schema(table).each do |fields|
|
147
|
+
tablefields << fields[0]
|
148
|
+
end
|
149
|
+
bind(tablefields)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Use this to bind a class to a table, so new objects created with template will match fields in database
|
153
|
+
def bind(fields)
|
154
|
+
|
155
|
+
if fields.empty?
|
156
|
+
raise ArgumentError, "Trying to bind object to empty list of fields..."
|
157
|
+
end
|
158
|
+
|
159
|
+
if (@@_template rescue nil) != nil
|
160
|
+
raise ArgumentError, "Class already bound to table: #{@@_template.to_s}. Use method rebind if new binding is needed", caller(1)
|
161
|
+
end
|
162
|
+
@@_template = {}
|
163
|
+
fields.each { |field| @@_template[field] = nil}
|
164
|
+
return
|
165
|
+
end
|
166
|
+
|
167
|
+
# Method can rebind fields if database changes dynamicly.... I'm not sure you should need this one....
|
168
|
+
def rebind!(fields)
|
169
|
+
@@_template = {}
|
170
|
+
fields.each { |field| @@_template[field] = nil}
|
171
|
+
return
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
def find(db,search_criteria)
|
176
|
+
db[@@_table].filter(search_criteria)
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
def delete!(db,search_criteria)
|
181
|
+
db[@@_table].filter(search_criteria).delete
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
def update!(db,search_criteria, update_criteria)
|
186
|
+
db[@@_table ].filter(search_criteria).update(update_criteria)
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
|
191
|
+
def template(*args)
|
192
|
+
the_instance = self.new(*args)
|
193
|
+
the_instance.load(@@_template)
|
194
|
+
the_instance
|
195
|
+
end
|
196
|
+
|
197
|
+
def find(db,search_criteria)
|
198
|
+
create(db[@@_table].where(search_criteria))
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sequel'
|
3
|
+
require '../lib/dbstruct'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
class AnObject
|
7
|
+
include DBStruct
|
8
|
+
@non_persisted_field
|
9
|
+
attr_accessor :non_persisted_field
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
@non_persisted_field = args[0]
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def return_value_from_field
|
17
|
+
return @non_persisted_field
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class CreateDb < Sequel::Migration
|
22
|
+
def up
|
23
|
+
create_table :person do
|
24
|
+
primary_key :pkid
|
25
|
+
text :name
|
26
|
+
float :amount
|
27
|
+
integer :age
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
class DBStruct_spec
|
34
|
+
DB = Sequel.sqlite '', :logger => [Logger.new($stdout)]
|
35
|
+
CreateDb.apply(DB,:up)
|
36
|
+
AnObject.bind_table(DB,:person)
|
37
|
+
|
38
|
+
def self.create_test_object
|
39
|
+
r = AnObject.template
|
40
|
+
r.name = 'Donald'
|
41
|
+
r.amount = 10.1
|
42
|
+
r.age = 77
|
43
|
+
return r
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
context "Create empty object and access fields" do
|
48
|
+
setup do
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
specify "All fields should have accesors based on table names" do
|
53
|
+
r = DBStruct_spec.create_test_object
|
54
|
+
|
55
|
+
r.name.should == 'Donald'
|
56
|
+
r.amount.should == 10.1
|
57
|
+
r.age.should == 77
|
58
|
+
end
|
59
|
+
|
60
|
+
specify "should raise error when accessing field that doesn't exist in database or object" do
|
61
|
+
r = AnObject.template
|
62
|
+
lambda {r.field_does_nt_exist}.should raise_error
|
63
|
+
end
|
64
|
+
|
65
|
+
specify "should be able to accesses transient (non persisted) values" do
|
66
|
+
r = AnObject.template
|
67
|
+
r.non_persisted_field = 'BOO'
|
68
|
+
r.non_persisted_field.should == 'BOO'
|
69
|
+
end
|
70
|
+
|
71
|
+
specify "should be able to pass variables to object constructor (initialize)" do
|
72
|
+
r = AnObject.template('BOO')
|
73
|
+
r.non_persisted_field.should == 'BOO'
|
74
|
+
end
|
75
|
+
|
76
|
+
specify "should be able to call ordinary method on persisted object" do
|
77
|
+
r = AnObject.template("BOO")
|
78
|
+
r.return_value_from_field.should == "BOO"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
context "Use instance to perform insert, read, update and delete" do
|
84
|
+
setup do
|
85
|
+
end
|
86
|
+
|
87
|
+
specify "should be able to insert and read object" do
|
88
|
+
i = DBStruct_spec.create_test_object
|
89
|
+
|
90
|
+
lambda {i.insert(DB)}.should_not raise_error
|
91
|
+
from_db = AnObject.find(DB,{:age => 77}).first
|
92
|
+
from_db.name == i.name.should
|
93
|
+
from_db.amount == i.amount.should
|
94
|
+
from_db.age == i.age.should
|
95
|
+
|
96
|
+
i.amount = 12.1
|
97
|
+
i.update(DB)
|
98
|
+
from_db = AnObject.find(DB,{:age => 77}).first
|
99
|
+
from_db.name == i.name.should
|
100
|
+
from_db.amount == i.amount.should
|
101
|
+
from_db.age == i.age.should
|
102
|
+
|
103
|
+
lambda {AnObject.delete!(DB,{:age => 77})}.should_not raise_error
|
104
|
+
#i.delete(DB)
|
105
|
+
deleted = AnObject.find(DB,{:age => 77})
|
106
|
+
deleted.should == []
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "Using class to perform statements that changes multiple rows" do
|
111
|
+
specify "should update multiple rows" do
|
112
|
+
i1 = DBStruct_spec.create_test_object
|
113
|
+
i1.insert(DB)
|
114
|
+
i2 = DBStruct_spec.create_test_object
|
115
|
+
i2.insert(DB)
|
116
|
+
lambda {AnObject.update!(DB, {:name => 'Donald'}, :name => 'Dolly')}.should_not raise_error
|
117
|
+
|
118
|
+
from_db = AnObject.find(DB,{:name => 'Dolly'}).first
|
119
|
+
from_db.name.should == 'Dolly'
|
120
|
+
end
|
121
|
+
|
122
|
+
specify "should delete multiple rows" do
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "Use indirect approach to save and store objects" do
|
128
|
+
setup do
|
129
|
+
end
|
130
|
+
|
131
|
+
specify "should be able to insert and read object indirectly" do
|
132
|
+
r = DBStruct_spec.create_test_object
|
133
|
+
r.age = 88
|
134
|
+
DB[:person].insert(r.save)
|
135
|
+
|
136
|
+
row = DB[:person].filter({:age => 88})
|
137
|
+
from_db = AnObject.create(row).first
|
138
|
+
from_db.name.should == r.name
|
139
|
+
from_db.age.should == r.age
|
140
|
+
from_db.amount == r.amount
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mudnaes-dbstruct
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Morten Udn\xC3\xA6s"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-12-03 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: diff-lcs
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.0.0
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: mime-types
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">"
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 0.0.0
|
32
|
+
version:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: open4
|
35
|
+
version_requirement:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.0.0
|
41
|
+
version:
|
42
|
+
description: This is a very simple framework to be able to access database rows as objects without forcing you to inherit from a Model class. Instead you create the class-hierarchy you want and use mixin to add persistance functionality to the object.
|
43
|
+
email: mudnaes@gmail.com
|
44
|
+
executables: []
|
45
|
+
|
46
|
+
extensions: []
|
47
|
+
|
48
|
+
extra_rdoc_files:
|
49
|
+
- README
|
50
|
+
files:
|
51
|
+
- README
|
52
|
+
- dbstruct.gemspec
|
53
|
+
- lib/dbstruct.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://github.com/mudnaes/dbstruct/tree/master
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --main
|
59
|
+
- README.txt
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.2.0
|
78
|
+
signing_key:
|
79
|
+
specification_version: 2
|
80
|
+
summary: Easy to use library for mapping rows between objects and database
|
81
|
+
test_files:
|
82
|
+
- spec/dbstruct_spec.rb
|