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.
Files changed (5) hide show
  1. data/README +56 -0
  2. data/dbstruct.gemspec +21 -0
  3. data/lib/dbstruct.rb +204 -0
  4. data/spec/dbstruct_spec.rb +143 -0
  5. 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.
@@ -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
+
@@ -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