Class: Momomoto::Table
Overview
This class implements access to tables/views. It must not be used directly but you should inherit from this class.
Direct Known Subclasses
Class Method Summary collapse
-
.base_table? ⇒ Boolean
is this a base table.
-
.delete(row) ⇒ Object
delete row from table.
-
.full_name ⇒ Object
get the full name of table including, if set, schema.
-
.insert(row) ⇒ Object
Creates an insert statement for a row.
-
.new(fields = {}) ⇒ Object
constructor for a record in this table accepts a hash with presets for the fields of the record.
-
.select(conditions = {}, options = {}) ⇒ Object
Searches for records and returns an Array containing the records.
-
.select_or_new(conditions = {}, options = {}) ⇒ Object
Tries to select the specified record or creates a new one if it does not find it.
-
.select_outer_join(conditions = {}, options = {}) ⇒ Object
experimental.
-
.select_single(conditions = {}, options = {}) ⇒ Object
Select a single row from the database raises Momomoto::Nothing_found if no row matched.
-
.update(row) ⇒ Object
Creates an update statement for a row.
-
.view? ⇒ Boolean
is this a view.
-
.write(row) ⇒ Object
Writes row back to database.
Methods inherited from Base
logical_operator=, momomoto_attribute, momomoto_attribute_reader
Class Method Details
.base_table? ⇒ Boolean
is this a base table
31 32 33 |
# File 'lib/momomoto/table.rb', line 31 def base_table? table_type == "BASE TABLE" end |
.delete(row) ⇒ Object
delete row from table
277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/momomoto/table.rb', line 277 def delete( row ) raise CriticalError, 'Deleting is only allowed for tables with primary keys' if primary_keys.empty? raise Error, "this is a new record" if row.new_record? conditions = {} primary_keys.each do | field_name | raise Error, "Primary key fields must not be empty!" if not row.send( field_name ) conditions[field_name] = row.send( field_name ) end sql = "DELETE FROM #{full_name} #{compile_where(conditions)};" row.new_record = true database.execute( sql ) end |
.full_name ⇒ Object
get the full name of table including, if set, schema
26 27 28 |
# File 'lib/momomoto/table.rb', line 26 def full_name "#{ schema_name ? schema_name + '.' : ''}#{table_name}" end |
.insert(row) ⇒ Object
Creates an insert statement for a row. Do not use it directly but use row.write or Table.write(row) instead.
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/momomoto/table.rb', line 224 def insert( row ) fields, values = [], [] columns.each do | field_name, datatype | # check for set primary key fields or fetch respective default values if primary_keys.member?( field_name ) && row.send( field_name ) == nil if datatype.default row.send( "#{field_name}=", database.execute("SELECT #{datatype.default};")[0][0] ) end if row.send( field_name ) == nil raise Error, "Primary key fields(#{field_name}) need to be set or must have a default" end end next if row.send( field_name ).nil? fields << field_name values << datatype.escape( row.get_column( field_name )) end raise Error, "insert with all fields nil" if fields.empty? sql = "INSERT INTO " + full_name + '(' + fields.join(',') + ') VALUES (' + values.join(',') + ');' row.new_record = false database.execute( sql ) end |
.new(fields = {}) ⇒ Object
constructor for a record in this table accepts a hash with presets for the fields of the record
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/momomoto/table.rb', line 143 def new( fields = {} ) initialize unless initialized new_row = const_get(:Row).new( [] ) new_row.new_record = true # set default values columns.each do | key, value | next if primary_keys.member?( key ) if value.default if value.default.match( /^\d+$/ ) new_row[ key ] = value.default elsif value.default == "true" new_row[ key ] = true elsif value.default == "false" new_row[ key ] = false elsif m = value.default.match( /^'([^']+)'::(text|interval|time(stamp)? with(out)? time zone)$/ ) new_row[ key ] = m[1] end end end fields.each do | key, value | new_row[ key ] = value end new_row end |
.select(conditions = {}, options = {}) ⇒ Object
Searches for records and returns an Array containing the records. There are a bunch of different use cases as this method is the primary way to access all rows in the database.
Selecting rows based on expression:
#selects the feeds that match both the given url and author fields
Posts.select(:feed_url => "https://www.c3d2.de/news-atom.xml",:author => "fnord")
Using order statements:
See Order#asc, Order#desc and Order#lower
#Selects conferences depending on start_date, starting with the oldest date.
#If two conferences start at the same date(day) use the second order parameter
#start_time.
Conference.select({},{:order => Momomoto.asc([:start_date,:start_time])} )
Using limit statement:
See Base#compile_limit
#selects five feeds
five_feeds = Feeds.select( {},{:limit => 5} )
Using offset statement:
See Base#compile_offset
#selects five feeds ommitting the first 23 rows
five_feeds = Feeds.select( {}, {:offset => 23, :limit => 5} )
Using logical operators:
See Datatype::Base#operator_sign for basic comparison operators
See Base#logical_operator for the supported logical operators
#selects the posts where the content field case-insensitevely matches
#"surveillance".
Posts.select( :content => {:ilike => 'surveillance'} )
#selects all conferences with a start_date before the current time.
Conferences.select( :start_date => {:le => Time.now} )
feed1 = "https://www.c3d2.de/news-atom.xml"
feed2 = "http://www.c3d2.de/news-atom.xml"
#selects the feeds with a field url that matches either feed1 or feed2
Feeds.select( :OR=>{:url => [feed1,feed2]} )
Selecting only given columns:
See Base#initialize_row for the implementation
#selects title and content for every row found in table Posts.
posts = Posts.select({},{:columns => [:title,:content]} )
The returned rows are special. They do not contain getter and setter
for the rest of the columns of the row. Only the specified columns
and all the primary keys of the table have proper accessor methods.
However, you can still change the rows and write them back to database:
posts.first.title = "new title"
posts.first.write
98 99 100 101 102 103 104 105 106 107 |
# File 'lib/momomoto/table.rb', line 98 def select( conditions = {}, = {} ) initialize unless initialized row_class = build_row_class( ) sql = compile_select( conditions, ) data = [] database.execute( sql ).each do | row | data << row_class.new( row ) end data end |
.select_or_new(conditions = {}, options = {}) ⇒ Object
Tries to select the specified record or creates a new one if it does not find it. Raises an exception if multiple records are found. You can pass a block which has to deliver the respective values for the primary key fields.
# selects the feed row matching the specified URL or creates a new
# row based on the given URL.
Feeds.select_or_new( :url => "https://www.c3d2.de/news-atom.xml" )
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/momomoto/table.rb', line 176 def select_or_new( conditions = {}, = {} ) begin if block_given? conditions = conditions.dup primary_keys.each do | field | conditions[ field ] = yield( field ) if not conditions[ field ] raise ConversionError if not conditions[ field ] end end rows = select( conditions, ) rescue ConversionError end if rows && rows.length > 1 raise Too_many_records, "Multiple values found in select_or_new for #{self}:#{conditions.inspect}" elsif rows && rows.length == 1 rows.first else new( [:copy_values] != false ? conditions : {} ) end end |
.select_outer_join(conditions = {}, options = {}) ⇒ Object
experimental
Searches for records and returns an array containing the records
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/momomoto/table.rb', line 112 def select_outer_join( conditions = {}, = {} ) initialize unless initialized join_table = [:join] fields = columns.keys.map{|field| full_name+'."'+field.to_s+'"'} fields += join_table.columns.keys.map{|field| join_table.full_name+'."'+field.to_s+'"'} sql = "SELECT " + fields.join( "," ) + " FROM " sql += full_name sql += " LEFT OUTER JOIN " + join_table.full_name + " USING(#{join_columns(join_table).join(',')})" sql += compile_where( conditions ) sql += compile_order( [:order] ) if [:order] sql += compile_limit( [:limit] ) if [:limit] sql += compile_offset( [:offset] ) if [:offset] data = {} database.execute( sql ).each do | row | new_row = row[0, columns.keys.length] data[new_row] ||= [] join_row = row[columns.keys.length,join_table.columns.keys.length] data[new_row] << join_table.const_get(:Row).new( join_row ) if join_row.nitems > 0 end result = [] data.each do | new_row, join_row | new_row = const_get(:Row).new( new_row ) new_row.instance_variable_set(:@join, join_row) new_row.send( :instance_eval ) { class << self; self; end }.send(:define_method, join_table.table_name ) do join_row end result << new_row end result end |
.select_single(conditions = {}, options = {}) ⇒ Object
Select a single row from the database raises Momomoto::Nothing_found if no row matched. raises Momomoto::Too_many_records if more than one record was found.
200 201 202 203 204 205 206 207 |
# File 'lib/momomoto/table.rb', line 200 def select_single( conditions = {}, = {} ) data = select( conditions, ) case data.length when 0 then raise Nothing_found, "nothing found in #{full_name}" when 1 then return data[0] else raise Too_many_records, "too many records found in #{full_name}" end end |
.update(row) ⇒ Object
Creates an update statement for a row. Do not call update directly but use row.write or Table.write(row) instead.
Get the value of row.new_record? before writing to database to find out if you are updating a row that already exists in the database.
feed = Feeds.select_single( :url => "http://www.c3d2.de/news-atom.xml" )
feed.new_record? => false
feed[:url] = "https://www.c3d2.de/news-atom.xml"
feed.new_record? => false
feed = Feeds.select_or_new( :url => "http://astroblog.spaceboyz.net/atom.rb" )
feed.new_record? => true
feed.write => true
feed.new_record? => false
261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/momomoto/table.rb', line 261 def update( row ) raise CriticalError, 'Updating is only allowed for tables with primary keys' if primary_keys.empty? setter, conditions = [], {} row.class.columns.each do | field_name, data_type | next if not row.dirty.member?( field_name ) setter << field_name.to_s + ' = ' + data_type.escape(row.get_column(field_name)) end primary_keys.each do | field_name | raise Error, "Primary key fields must not be empty!" if not row.send( field_name ) conditions[field_name] = row.send( field_name ) end sql = 'UPDATE ' + full_name + ' SET ' + setter.join(',') + compile_where( conditions ) + ';' database.execute( sql ) end |
.view? ⇒ Boolean
is this a view
36 37 38 |
# File 'lib/momomoto/table.rb', line 36 def view? table_type == "VIEW" end |
.write(row) ⇒ Object
Writes row back to database. This method is called by Momomoto::Row#write
211 212 213 214 215 216 217 218 219 220 |
# File 'lib/momomoto/table.rb', line 211 def write( row ) # :nodoc: if row.new_record? insert( row ) else return false unless row.dirty? update( row ) end row.clean_dirty true end |