INSTANCE METHODS: Override SQL based methods in ActiveRecord::Base Instance methods: everything invoked from records instances, e.g. book = Book.find(:first); book.destroy
- apply_options_to_result_set
- attributes_to_input_rec
- begin_db_transaction
- build_conditions_block
- build_conditions_from_options
- commit_db_transaction
- construct_conditions_from_arguments
- count
- count_by_sql
- create_without_callbacks
- db
- db
- decrement_counter
- delete
- delete_all
- destroy_without_callbacks
- find
- find_by_sql
- increment_counter
- instantiate_records
- parse_conditions_from_sql_array
- parse_updates_from_sql_array
- parse_updates_from_sql_string
- rollback_db_transaction
- select_all
- select_one
- serialize
- table
- table
- translate_sql_to_code
- type_condition
- update_all
- update_with_lock
- update_without_callbacks
- update_without_lock
| SQL_FRAGMENT_TRANSLATIONS | = | [ [/1\s*=\s*1/, 'true'], ['rec.', ''], ['==', '='], [/(\w+)\s*=\s*/, 'rec.\1 == '], [/(\w+)\s*<>\s*?/, 'rec.\1 !='], [/(\w+)\s*<\s*?/, 'rec.\1 <'], [/(\w+)\s*>\s*?/, 'rec.\1 >'], [/(\w+)\s*IS\s+NOT\s*?/, 'rec.\1 !='], [/(\w+)\s*IS\s*?/, 'rec.\1 =='], [/(\w+)\s+IN\s+/, 'rec.\1.in'], [/\.id(\W)/i, '.recno\1'], ['<>', '!='], [/\bNULL\b/i, 'nil'], [/\bAND\b/i, 'and'], [/\bOR\b/i, 'or'], ["'%s'", '?'], ['%d', '?'], [/:\w+/, '?'], [/\bid\b/i, 'rec.recno'], ] |
| TODO: handle LIKE | ||
NOT SUPPORTED!!!
[ show source ]
# File kirbybase_adapter.rb, line 674
674: def self.begin_db_transaction
675: raise ArgumentError, "#begin_db_transaction called"
676: # connection.transaction
677: end
Builds the :conditions block from various forms of input.
- Procs are passed as is
- Arrays are assumed to be in the format of [‘name = ?’, ‘Assaph’]
- Fragment String are translated to code Full SQL statements will raise an error
- No parameters will assume a true for all records
[ show source ]
# File kirbybase_adapter.rb, line 607
607: def self.build_conditions_block conditions
608: case conditions
609: when Proc then conditions
610: when Array then parse_conditions_from_sql_array(conditions)
611: when String
612: if conditions.match(/^(SELECT|INSERT|DELETE|UPDATE)/i)
613: raise ArgumentError, "KirbyBase does not support SQL for :conditions! '#{conditions.inspect}''"
614: else
615: conditions_string = translate_sql_to_code(conditions)
616: lambda{|rec| eval conditions_string }
617: end
618:
619: when nil
620: if block_given?
621: Proc.new
622: else
623: lambda{|r| true}
624: end
625: end # case conditions
626: end
One of the main methods: Assembles the :conditions block from the options argument (See build_conditions_block for actual translation). Then adds the scope and inheritance-type conditions (if present).
[ show source ]
# File kirbybase_adapter.rb, line 549
549: def self.build_conditions_from_options options
550: basic_selector_block = case options
551: when Array
552: if options[0].is_a? Proc
553: options[0]
554: elsif options.flatten.length == 1
555: translate_sql_to_code options.flatten[0]
556: else
557: parse_conditions_from_sql_array options.flatten
558: end
559:
560: when Hash
561: build_conditions_block options[:conditions]
562:
563: when Proc
564: options
565:
566: else
567: raise ArgumentError, "Don't know how to process (#{options.inspect})"
568: end
569:
570: selector_with_scope = if scope(:find, :conditions)
571: scope_conditions_block = build_conditions_block(scope(:find, :conditions))
572: lambda{|rec| basic_selector_block[rec] && scope_conditions_block[rec]}
573: else
574: basic_selector_block
575: end
576:
577: conditions_block = if descends_from_active_record?
578: selector_with_scope
579: else
580: untyped_conditions_block = selector_with_scope
581: type_condition_block = type_condition(options.is_a?(Hash) ? options[:class_name] : nil)
582: lambda{|rec| type_condition_block[rec] && untyped_conditions_block[rec]}
583: end
584:
585: conditions_block
586: end
NOT SUPPORTED!!!
[ show source ]
# File kirbybase_adapter.rb, line 680
680: def self.commit_db_transaction
681: raise ArgumentError, "#commit_db_transaction"
682: # connection.commit
683: end
Override of AR::Base SQL construction to build a conditions block. Used only by AR::Base#method_missing to support dynamic finders (e.g. find_by_name).
[ show source ]
# File kirbybase_adapter.rb, line 416
416: def self.construct_conditions_from_arguments(attribute_names, arguments)
417: conditions = []
418: attribute_names.each_with_index { |name, idx| conditions << "#{name} #{attribute_condition(arguments[idx])} " }
419: build_conditions_from_options :conditions => [ conditions.join(" and ").strip, *arguments[0...attribute_names.length] ]
420: end
May also be called with a block, e.g.:
Book.count {|rec| rec.author_id == @author.id}
[ show source ]
# File kirbybase_adapter.rb, line 661
661: def self.count(*args)
662: if args.compact.empty?
663: if block_given?
664: find(:all, :conditions => Proc.new).size
665: else
666: self.find(:all).size
667: end
668: else
669: self.find(:all, :conditions => build_conditions_from_options(args)).size
670: end
671: end
NOT SUPPORTED !!!
[ show source ]
# File kirbybase_adapter.rb, line 339
339: def self.count_by_sql(*args)
340: raise StatementInvalid, "SQL not Supported"
341: end
The KirbyBase object
[ show source ]
# File kirbybase_adapter.rb, line 298
298: def self.db
299: #db ||= connection.db
300: connection.db
301: end
Override of AR::Base that was using raw SQL
[ show source ]
# File kirbybase_adapter.rb, line 430
430: def self.decrement_counter(counter_name, ids)
431: [ids].flatten.each do |id|
432: table.update{|rec| rec.recno == id }.set{ |rec| rec.send "#{counter_name}=", (rec.send(counter_name)-1) }
433: end
434: end
Deletes the selected rows from the DB.
[ show source ]
# File kirbybase_adapter.rb, line 344
344: def self.delete(ids)
345: ids = [ids].flatten
346: table.delete {|r| ids.include? r.recno }
347: end
Deletes the matching rows from the table. If no conditions are specified, will clear the whole table.
[ show source ]
# File kirbybase_adapter.rb, line 351
351: def self.delete_all(conditions = nil)
352: if conditions.nil? and !block_given?
353: table.delete_all
354: else
355: table.delete &build_conditions_from_options(:conditions => conditions)
356: end
357: end
This methods differs in the API from ActiveRecord::Base#find! The changed options are:
- :conditions this should be a block for selecting the records
- :order this should be the symbol of the field name
- :include: Names associations that should be loaded alongside using KirbyBase Lookup fields
The following work as before:
- :offset: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
- :readonly: Mark the returned records read-only so they cannot be saved or updated.
- :limit: Max numer of records returned
- :select: Field names from the table. Not as useful, as joins are irrelevant
The following are not supported (silently ignored);
- :joins: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id".
As a more Kirby-ish way, you can also pass a block to find that will be used to select the matching records. It’s a shortcut to :conditions.
[ show source ]
# File kirbybase_adapter.rb, line 451
451: def self.find(*args)
452: options = extract_options_from_args!(args)
453: conditions = Proc.new if block_given?
454: raise ArgumentError, "Please specify EITHER :conditions OR a block!" if conditions and options[:conditions]
455: options[:conditions] ||= conditions
456: options[:conditions] = build_conditions_from_options(options)
457: filter = options[:select] ? [:recno, options[:select]].flatten.map{|s| s.to_sym} : nil
458:
459: # Inherit :readonly from finder scope if set. Otherwise,
460: # if :joins is not blank then :readonly defaults to true.
461: unless options.has_key?(:readonly)
462: if scoped?(:find, :readonly)
463: options[:readonly] = scope(:find, :readonly)
464: elsif !options[:joins].blank?
465: options[:readonly] = true
466: end
467: end
468:
469: case args.first
470: when :first
471: return find(:all, options.merge(options[:include] ? { } : { :limit => 1 })).first
472: when :all
473: records = options[:include] ?
474: find_with_associations(options) :
475: filter ? table.select( *filter, &options[:conditions] ) : table.select( &options[:conditions] )
476: records = apply_options_to_result_set records, options
477: records = instantiate_records(records, :filter => filter, :readonly => options[:readonly])
478: records
479: else
480: return args.first if args.first.kind_of?(Array) && args.first.empty?
481: raise RecordNotFound, "Expecting a list of IDs!" unless args.flatten.all?{|i| i.is_a?(Numeric) || (i.is_a?(String) && i.match(/^\d+$/)) }
482:
483: expects_array = ( args.is_a?(Array) and args.first.kind_of?(Array) )
484: ids = args.flatten.compact.collect{ |i| i.to_i }.uniq
485:
486: records = filter ?
487: table.select_by_recno_index(*filter) { |r| ids.include?(r.recno) } :
488: table.select_by_recno_index { |r| ids.include?(r.recno) }
489: records = apply_options_to_result_set(records, options) rescue records
490:
491: conditions_message = options[:conditions] ? " and conditions: #{options[:conditions].inspect}" : ''
492: case ids.size
493: when 0
494: raise RecordNotFound, "Couldn't find #{name} without an ID#{conditions_message}"
495: when 1
496: if records.nil? or records.empty?
497: raise RecordNotFound, "Couldn't find #{name} with ID=#{ids.first}#{conditions_message}"
498: end
499: records = instantiate_records(records, :filter => filter, :readonly => options[:readonly])
500: expects_array ? records : records.first
501: else
502: if records.size == ids.size
503: return instantiate_records(records, :filter => filter, :readonly => options[:readonly])
504: else
505: raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids.join(', ')})#{conditions_message}"
506: end
507: end
508: end
509: end
NOT SUPPORTED !!!
[ show source ]
# File kirbybase_adapter.rb, line 334
334: def self.find_by_sql(*args)
335: raise StatementInvalid, "SQL not Supported"
336: end
Override of AR::Base that was using raw SQL
[ show source ]
# File kirbybase_adapter.rb, line 423
423: def self.increment_counter(counter_name, ids)
424: [ids].flatten.each do |id|
425: table.update{|rec| rec.recno == id }.set{ |rec| rec.send "#{counter_name}=", (rec.send(counter_name)+1) }
426: end
427: end
Attempt to parse parameters in the format of [‘name = ? AND value = ?’, some_name, 1] in the :conditions clause
[ show source ]
# File kirbybase_adapter.rb, line 393
393: def self.parse_conditions_from_sql_array(sql_parameters_array)
394: query = sql_parameters_array[0]
395: args = sql_parameters_array[1..-1].map{|arg| arg.is_a?(Hash) ? (raise PreparedStatementInvalid if arg.size > 1; arg.values[0]) : arg }
396:
397: query = translate_sql_to_code query
398: raise PreparedStatementInvalid if query.count('?') != args.size
399: query_components = query.split('?').zip(args.map{ |a|
400: case a
401: when String, Array then a.inspect
402: when nil then 'nil'
403: else a
404: end
405: })
406: block_string = query_components.to_s
407: begin
408: eval "lambda{ |rec| #{block_string} }"
409: rescue Exception => detail
410: raise PreparedStatementInvalid, detail.to_s
411: end
412: end
Attempt to parse parameters in the format of [‘name = ?’, some_name] for updates
[ show source ]
# File kirbybase_adapter.rb, line 376
376: def self.parse_updates_from_sql_array sql_parameters_array
377: updates_string = sql_parameters_array[0]
378: args = sql_parameters_array[1..-1]
379:
380: update_code = table.field_names.inject(updates_string) {|updates, fld| fld == :id ? updates.gsub(/\bid\b/, 'rec.recno') : updates.gsub(/\b(#{fld})\b/, 'rec.\1') }
381: update_code = update_code.split(',').zip(args).map {|i,v| [i.gsub('?', ''), v.inspect]}.to_s.gsub(/\bNULL\b/i, 'nil')
382: eval "lambda{ |rec| #{update_code} }"
383: end
Attempt to parse parameters in the format of ‘name = "Some Name"’ for updates
[ show source ]
# File kirbybase_adapter.rb, line 386
386: def self.parse_updates_from_sql_string sql_string
387: update_code = table.field_names.inject(sql_string) {|updates, fld| fld == :id ? updates.gsub(/\bid\b/, 'rec.recno') : updates.gsub(/\b(#{fld})\b/, 'rec.\1') }.gsub(/\bNULL\b/i, 'nil')
388: eval "lambda{ |rec| #{update_code} }"
389: end
NOT SUPPORTED!!!
[ show source ]
# File kirbybase_adapter.rb, line 686
686: def self.rollback_db_transaction
687: raise ArgumentError, "#rollback_db_transaction"
688: # connection.rollback
689: end
NOT SUPPORTED !!!
[ show source ]
# File kirbybase_adapter.rb, line 313
313: def self.select_all(sql, name = nil)
314: raise StatementInvalid, "select_all(#{sql}, #{name}"
315: execute(sql, name).map do |row|
316: record = {}
317: row.each_key do |key|
318: if key.is_a?(String)
319: record[key.sub(/^\w+\./, '')] = row[key]
320: end
321: end
322: record
323: end
324: end
NOT SUPPORTED !!!
[ show source ]
# File kirbybase_adapter.rb, line 327
327: def self.select_one(sql, name = nil)
328: raise StatementInvalid, "select_one(#{sql}, #{name}"
329: result = select_all(sql, name)
330: result.nil? ? nil : result.first
331: end
Serializing a column will cause it to change the column type to :YAML in the database.
[ show source ]
# File kirbybase_adapter.rb, line 696
696: def serialize(attr_name, class_name = Object)
697: __before_ackbar_serialize(attr_name, class_name)
698: connection.change_column(table_name, attr_name, :yaml)
699: end
The KBTable object for this AR model object
[ show source ]
# File kirbybase_adapter.rb, line 304
304: def self.table
305: begin
306: db.get_table(table_name.to_sym)
307: rescue RuntimeError => detail
308: raise StatementInvalid, detail.message
309: end
310: end
Translates SQL fragments to a code string. This code string can then be used to construct a code block for KirbyBase. Relies on the SQL_FRAGMENT_TRANSLATIONS series of transformations. Will also remove table names (e.g. people.name) so not safe to use for joins.
[ show source ]
# File kirbybase_adapter.rb, line 654
654: def self.translate_sql_to_code sql_string
655: block_string = SQL_FRAGMENT_TRANSLATIONS.inject(sql_string) {|str, (from, to)| str.gsub(from, to)}
656: block_string.gsub(/#{table_name}\./, '')
657: end
For handling the table inheritance column.
[ show source ]
# File kirbybase_adapter.rb, line 589
589: def self.type_condition class_name = nil
590: type_condition = if class_name
591: "rec.#{inheritance_column} == '#{class_name}'"
592: else
593: subclasses.inject("rec.#{inheritance_column}.to_s == '#{name.demodulize}' ") do |condition, subclass|
594: condition << "or rec.#{inheritance_column}.to_s == '#{subclass.name.demodulize}' "
595: end
596: end
597:
598: eval "lambda{ |rec| #{type_condition} }"
599: end
Updates the matching rows from the table. If no conditions are specified, will update all rows in the table.
[ show source ]
# File kirbybase_adapter.rb, line 361
361: def self.update_all(updates, conditions = nil)
362: finder = build_conditions_from_options :conditions => conditions
363: updater = case updates
364: when Proc then updates
365: when Hash then updates
366: when Array then parse_updates_from_sql_array(updates)
367: when String then parse_updates_from_sql_string(updates)
368: else raise ArgumentError, "Don't know how to process updates: #{updates.inspect}"
369: end
370: updater.is_a?(Proc) ?
371: table.update(&finder).set(&updater) :
372: table.update(&finder).set(updater)
373: end
Applies the limit/offset/readonly/order and other options to the result set. Will also reapply the conditions.
[ show source ]
# File kirbybase_adapter.rb, line 524
524: def self.apply_options_to_result_set records, options
525: records = [records].flatten.compact
526: records = records.select( &options[:conditions] ) if options[:conditions]
527: if options[:order]
528: options[:order].split(',').reverse.each do |order_field|
529: # this algorithm is probably incorrect for complex sorts, like
530: # col_a, col_b DESC, col_C
531: reverse = order_field =~ /\bDESC\b/i
532: order_field = order_field.strip.split[0] # clear any DESC, ASC
533: records = records.stable_sort_by(order_field.to_sym == :id ? :recno : order_field.to_sym)
534: records.reverse! if reverse
535: end
536: end
537: offset = options[:offset] || scope(:find, :offset)
538: records = records.slice!(offset..-1) if offset
539: limit = options[:limit] || scope(:find, :limit)
540: records = records.slice!(0, limit) if limit
541: records
542: end
Instantiates the model record-objects from the KirbyBase structs. Will also apply the limit/offset/readonly/order and other options.
[ show source ]
# File kirbybase_adapter.rb, line 513
513: def self.instantiate_records rec_array, options = {}
514: field_names = ['id', table.field_names[1..-1]].flatten.map { |f| f.to_s }
515: field_names &= ['id', options[:filter]].flatten.map{|f| f.to_s} if options[:filter]
516: records = [rec_array].flatten.compact.map { |rec| instantiate( field_names.zip(rec.values).inject({}){|h, (k,v)| h[k] = v; h} ) }
517: records.each { |record| record.readonly! } if options[:readonly]
518: records
519: end
translates the Active-Record instance attributes to a input hash for KirbyBase to be used in insert or update
[ show source ]
# File kirbybase_adapter.rb, line 763
763: def attributes_to_input_rec
764: field_types = Hash[ *table.field_names.zip(table.field_types).flatten ]
765: attributes.inject({}) do |irec, (key, val)|
766: irec[key.to_sym] = case field_types[key.to_sym]
767: when :Integer
768: case val
769: when false then 0
770: when true then 1
771: else val
772: end
773:
774: when :Boolean
775: case val
776: when 0 then false
777: when 1 then true
778: else val
779: end
780:
781: when :Date
782: val.is_a?(Time) ? val.to_date : val
783:
784: else val
785: end
786: irec
787: end
788: end
Creates a new record with values matching those of the instance attributes.
[ show source ]
# File kirbybase_adapter.rb, line 746
746: def create_without_callbacks
747: input_rec = attributes_to_input_rec
748: (input_rec.keys - table.field_names + [:id]).each {|unknown_attribute| input_rec.delete(unknown_attribute)}
749: self.id = table.insert(input_rec)
750: @new_record = false
751: end
KirbyBase DB Object
[ show source ]
# File kirbybase_adapter.rb, line 710
710: def db
711: self.class.db
712: end
Deletes the matching row for this object
[ show source ]
# File kirbybase_adapter.rb, line 754
754: def destroy_without_callbacks
755: unless new_record?
756: table.delete{ |rec| rec.recno == id }
757: end
758: freeze
759: end
Table for the AR Model class for this record
[ show source ]
# File kirbybase_adapter.rb, line 715
715: def table
716: self.class.table
717: end
Updates the associated record with values matching those of the instance attributes. Will also check for a lock (See ActiveRecord::Locking.
[ show source ]
# File kirbybase_adapter.rb, line 728
728: def update_with_lock
729: if locking_enabled?
730: previous_value = self.lock_version
731: self.lock_version = previous_value + 1
732:
733: pk = self.class.primary_key == 'id' ? :recno : :id
734: affected_rows = table.update(attributes_to_input_rec){|rec| rec.send(pk) == id and rec.lock_version == previous_value}
735:
736: unless affected_rows == 1
737: raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
738: end
739: else
740: update_without_lock
741: end
742: end
Alias for update_with_lock
Updates the associated record with values matching those of the instance attributes.
[ show source ]
# File kirbybase_adapter.rb, line 722
722: def update_without_lock
723: table.update{ |rec| rec.recno == id }.set(attributes_to_input_rec)
724: end