Class: Og::Adapter
Overview
An adapter communicates with the backend datastore. The adapters for all supported datastores extend this class. Typically, an RDBMS is used to implement a datastore.
Direct Known Subclasses
Instance Attribute Summary collapse
-
#typemap ⇒ Object
A mapping between Ruby and backend Datastore types.
Class Method Summary collapse
-
.date(date) ⇒ Object
Output YYY-mm-dd TODO: Optimize this.
-
.encode(klass) ⇒ Object
Encode the name of the klass as an sql safe string.
-
.escape(str) ⇒ Object
Escape an SQL string.
-
.for_name(name) ⇒ Object
Lookup the adapter instance from the adapter name.
-
.join_table(klass1, klass2) ⇒ Object
The name of the join table for the two given classes.
-
.parse_date(str) ⇒ Object
Input YYYY-mm-dd TODO: Optimize this.
-
.parse_timestamp(str) ⇒ Object
Parse sql datetime TODO: Optimize this.
-
.table(klass) ⇒ Object
The name of the SQL table where objects of this class are stored.
-
.timestamp(time = Time.now) ⇒ Object
Convert a ruby time to an sql timestamp.
Instance Method Summary collapse
-
#calc_field_index(klass, og) ⇒ Object
Generate the mapping of the database fields to the object properties.
-
#create_db(database, user = nil, password = nil) ⇒ Object
Create the database.
-
#create_fields(klass) ⇒ Object
Create the fields that correpsond to the klass properties.
-
#create_table(klass) ⇒ Object
Create the managed object table.
-
#drop_db(database, user = nil, password = nil) ⇒ Object
Drop the database.
-
#eval_og_insert(klass, db) ⇒ Object
Precompile the insert code for the given class.
-
#eval_og_oid(klass) ⇒ Object
Generate the property for oid.
-
#eval_og_read(klass, db) ⇒ Object
Precompile the code to read (deserialize) objects of the given class from the backend.
-
#eval_og_update(klass, db) ⇒ Object
Precompile the update code for the given class.
-
#initialize ⇒ Adapter
constructor
A new instance of Adapter.
-
#insert_code(klass, sql, pre_cb, post_cb) ⇒ Object
Returns the code that actually inserts the object into the database.
-
#new_connection(db) ⇒ Object
Create a new connection to the backend.
-
#props_for_insert(klass) ⇒ Object
Returns the props that will be included in the insert query.
-
#read_prop(p, idx) ⇒ Object
Return an evaluator for reading the property.
-
#write_prop(p) ⇒ Object
Return an sql string evaluator for the property.
Constructor Details
#initialize ⇒ Adapter
Returns a new instance of Adapter.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/og/adapter.rb', line 31 def initialize @typemap = { Integer => 'integer', Fixnum => 'integer', Float => 'float', String => 'text', Time => 'timestamp', Date => 'date', TrueClass => 'boolean', Object => 'text', Array => 'text', Hash => 'text' } end |
Instance Attribute Details
#typemap ⇒ Object
A mapping between Ruby and backend Datastore types.
22 23 24 |
# File 'lib/og/adapter.rb', line 22 def typemap @typemap end |
Class Method Details
.date(date) ⇒ Object
Output YYY-mm-dd TODO: Optimize this
68 69 70 71 |
# File 'lib/og/adapter.rb', line 68 def self.date(date) return nil unless date return "#{date.year}-#{date.month}-#{date.mday}" end |
.encode(klass) ⇒ Object
Encode the name of the klass as an sql safe string. The Module separators are replaced with _ and NOT stripped out so that we can convert back to the original notation if needed. The leading module if available is removed.
119 120 121 |
# File 'lib/og/adapter.rb', line 119 def self.encode(klass) "#{klass.name.gsub(/^.*::/, "")}".gsub(/::/, "_").downcase end |
.escape(str) ⇒ Object
Escape an SQL string
52 53 54 55 |
# File 'lib/og/adapter.rb', line 52 def self.escape(str) return nil unless str return str.gsub( /'/, "''" ) end |
.for_name(name) ⇒ Object
Lookup the adapter instance from the adapter name.
26 27 28 29 |
# File 'lib/og/adapter.rb', line 26 def self.for_name(name) require "og/adapters/#{name}" eval %{ return #{name.capitalize}Adapter.instance } end |
.join_table(klass1, klass2) ⇒ Object
The name of the join table for the two given classes.
132 133 134 |
# File 'lib/og/adapter.rb', line 132 def self.join_table(klass1, klass2) "_#{Og.table_prefix}j_#{encode(klass1)}_#{encode(klass2)}" end |
.parse_date(str) ⇒ Object
Input YYYY-mm-dd TODO: Optimize this
83 84 85 86 |
# File 'lib/og/adapter.rb', line 83 def self.parse_date(str) return nil unless str return Date.strptime(str) end |
.parse_timestamp(str) ⇒ Object
Parse sql datetime TODO: Optimize this
76 77 78 |
# File 'lib/og/adapter.rb', line 76 def self.(str) return Time.parse(str) end |
.table(klass) ⇒ Object
The name of the SQL table where objects of this class are stored.
126 127 128 |
# File 'lib/og/adapter.rb', line 126 def self.table(klass) "_#{Og.table_prefix}#{encode(klass)}" end |
.timestamp(time = Time.now) ⇒ Object
Convert a ruby time to an sql timestamp. TODO: Optimize this
60 61 62 63 |
# File 'lib/og/adapter.rb', line 60 def self.(time = Time.now) return nil unless time return time.strftime("%Y-%m-%d %H:%M:%S") end |
Instance Method Details
#calc_field_index(klass, og) ⇒ Object
Generate the mapping of the database fields to the object properties.
237 238 239 |
# File 'lib/og/adapter.rb', line 237 def calc_field_index(klass, og) raise 'Not implemented!' end |
#create_db(database, user = nil, password = nil) ⇒ Object
Create the database.
94 95 96 |
# File 'lib/og/adapter.rb', line 94 def create_db(database, user = nil, password = nil) Logger.info "Creating database '#{database}'." end |
#create_fields(klass) ⇒ Object
Create the fields that correpsond to the klass properties. The generated fields array is used in create_table. If the property has an :sql metadata this overrides the default mapping. If the property has an :extra_sql metadata the extra sql is appended after the default mapping.
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/og/adapter.rb', line 188 def create_fields(klass) fields = [] klass.__props.each do |p| klass.sql_index(p.symbol) if p.[:sql_index] field = "#{p.symbol}" if p. and p.[:sql] field << " #{p.[:sql]}" else field << " #{@typemap[p.klass]}" # attach extra sql if p. and extra_sql = p.[:extra_sql] field << " #{extra_sql}" end end fields << field end return fields end |
#create_table(klass) ⇒ Object
Create the managed object table. The properties of the object are mapped to the table columns. Additional sql relations and constrains are created (indicices, sequences, etc).
216 217 218 |
# File 'lib/og/adapter.rb', line 216 def create_table(klass) raise 'Not implemented!' end |
#drop_db(database, user = nil, password = nil) ⇒ Object
Drop the database.
100 101 102 |
# File 'lib/og/adapter.rb', line 100 def drop_db(database, user = nil, password = nil) Logger.info "Dropping database '#{database}'." end |
#eval_og_insert(klass, db) ⇒ Object
Precompile the insert code for the given class. The generated code sets the oid when inserting!
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/og/adapter.rb', line 256 def eval_og_insert(klass, db) if klass.instance_methods.include?('og_pre_insert') pre_cb = 'og_pre_insert(conn);' else pre_cb = '' end if klass.instance_methods.include?('og_post_insert') post_cb = 'og_post_insert(conn);' else post_cb = '' end if klass.instance_methods.include?('og_pre_insert_update') pre_cb << 'og_pre_insert_update(conn);' end if klass.instance_methods.include?('og_post_insert_update') post_cb << 'og_post_insert_update(conn);' end klass.class_eval %{ def og_insert(conn) #{insert_code(klass, db, pre_cb, post_cb)} end } end |
#eval_og_oid(klass) ⇒ Object
Generate the property for oid.
247 248 249 250 251 |
# File 'lib/og/adapter.rb', line 247 def eval_og_oid(klass) klass.class_eval %{ prop_accessor :oid, Fixnum, :sql => "integer PRIMARY KEY" } end |
#eval_og_read(klass, db) ⇒ Object
Precompile the code to read (deserialize) objects of the given class from the backend. In order to allow for changing field/attribute orders we have to use a field mapping hash.
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/og/adapter.rb', line 329 def eval_og_read(klass, db) calc_field_index(klass, db) props = klass.__props code = [] props.each do |p| if idx = db.managed_classes[klass].field_index[p.name] # more fault tolerant if a new field is added and it # doesnt exist in the database. code << "@#{p.name} = #{read_prop(p, idx)}" end end klass.class_eval %{ def og_read(res, tuple = nil) #{code.join('; ')} end } end |
#eval_og_update(klass, db) ⇒ Object
Precompile the update code for the given class. Ignore the oid when updating!
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/og/adapter.rb', line 287 def eval_og_update(klass, db) props = klass.__props.reject { |p| :oid == p.symbol } updates = props.collect { |p| "#{p.name}=#{write_prop(p)}" } sql = "UPDATE #{klass::DBTABLE} SET #{updates.join(', ')} WHERE oid=#\{@oid\}" if klass.instance_methods.include?('og_pre_update') pre_cb = 'og_pre_update(conn);' else pre_cb = '' end if klass.instance_methods.include?('og_post_update') post_cb = 'og_post_update(conn);' else post_cb = '' end if klass.instance_methods.include?('og_pre_insert_update') pre_cb << 'og_pre_insert_update(conn);' end if klass.instance_methods.include?('og_post_insert_update') post_cb << 'og_post_insert_update(conn);' end klass.class_eval %{ def og_update(conn) #{pre_cb} conn.exec "#{sql}" #{post_cb} end } end |
#insert_code(klass, sql, pre_cb, post_cb) ⇒ Object
Returns the code that actually inserts the object into the database. Returns the code as String.
230 231 232 |
# File 'lib/og/adapter.rb', line 230 def insert_code(klass, sql, pre_cb, post_cb) raise 'Not implemented!' end |
#new_connection(db) ⇒ Object
Create a new connection to the backend.
106 107 108 |
# File 'lib/og/adapter.rb', line 106 def new_connection(db) return Og::Connection.new(db) end |
#props_for_insert(klass) ⇒ Object
Returns the props that will be included in the insert query. For some backends the oid should be stripped.
223 224 225 |
# File 'lib/og/adapter.rb', line 223 def props_for_insert(klass) klass.__props end |
#read_prop(p, idx) ⇒ Object
Return an evaluator for reading the property. No need to optimize this, used only to precalculate code.
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/og/adapter.rb', line 164 def read_prop(p, idx) if p.klass.ancestors.include?(Integer) return "res[#{idx}].to_i" elsif p.klass.ancestors.include?(Float) return "res[#{idx}].to_f" elsif p.klass.ancestors.include?(String) return "res[#{idx}]" elsif p.klass.ancestors.include?(Time) return "#{self.class}.parse_timestamp(res[#{idx}])" elsif p.klass.ancestors.include?(Date) return "#{self.class}.parse_date(res[#{idx}])" elsif p.klass.ancestors.include?(TrueClass) return "('0' != res[#{idx}])" else return "YAML::load(res[#{idx}])" end end |
#write_prop(p) ⇒ Object
Return an sql string evaluator for the property. No need to optimize this, used only to precalculate code. YAML is used to store general Ruby objects to be more portable.
FIXME: add extra handling for float.
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/og/adapter.rb', line 143 def write_prop(p) if p.klass.ancestors.include?(Integer) return "#\{@#{p.symbol} || 'NULL'\}" elsif p.klass.ancestors.include?(Float) return "#\{@#{p.symbol} || 'NULL'\}" elsif p.klass.ancestors.include?(String) return "'#\{#{self.class}.escape(@#{p.symbol})\}'" elsif p.klass.ancestors.include?(Time) return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}| elsif p.klass.ancestors.include?(Date) return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}| elsif p.klass.ancestors.include?(TrueClass) return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}" else return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}| end end |