# SmqlToAR - Base library: Converts SMQL to ActiveRecord # Copyright (C) 2011 Denis Knauf # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . class SmqlToAR module Assertion def raise_unless cond, exception = nil, *args cond, exception, *args = yield. cond, exception, *args if block_given? raise exception || Exception, *args unless cond end def raise_if cond, exception = nil, *args cond, exception, *args = yield. cond, exception, *args if block_given? raise exception || Exception, *args if cond end end include ActiveSupport::Benchmarkable ############################################################################r # Exceptions class SMQLError < Exception attr_reader :data def initialize data @data = data super data.inspect end end # Ein Fehler ist in einem Subquery aufgetreten. # Die eigentliche Exception wird in @data[:exception] hinterlegt. class SubSMQLError < SMQLError def initialize query, model, exception ex = {class: exception.class, message: exception.message} ex[:data] = exception.data if exception.respond_to? :data super query: query, model: model.to_s, exception: ex set_backtrace exception.backtrace end end class ParseError < SMQLError; end # Malformed ColOp class UnexpectedColOpError < ParseError def initialize model, colop, val super got: colop, val: val, model: model.to_s end end class UnexpectedError < ParseError def initialize model, colop, val super got: {colop => val}, model: model.to_s end end class NonExistingSelectableError < SMQLError def initialize got super got: got end end class NonExistingColumnError < SMQLError def initialize expected, got super expected: expected, got: got end end class NonExistingRelationError < SMQLError def initialize expected, got super expected: expected, got: got end end class ProtectedColumnError < SMQLError def initialize protected_column super protected_column: protected_column end end class RootOnlyFunctionError < SMQLError def initialize path super path: path end end class ConColumnError < SMQLError def initialize expected, got super expected: expected, got: got end end class BuilderError < Exception; end ############################################################################# # Model der Relation `rel` von `model` def self.model_of model, rel r = model.reflections[ rel.to_sym].andand.klass r.nil? && rel === :self ? model : r end # Eine Spalte in einer Tabelle, relativ zu `Column#model`. # Kann auch einen Pfad `Column#path` haben, wobei `Column#col` dann # eine Relation von dem Model der letzten Relation von `Column#path` ist. class Column include Enumerable attr_reader :path, :col attr_accessor :model class Col include Comparable attr_accessor :col, :as def initialize col, as = nil if col.kind_of? Col @col, @as = col.col, col.as elsif /^(.*?)\[(.*)\]$/ =~ col.to_s @col, @as = $1, $2 else @col, @as = col.to_s, as.to_s @as = nil if @as.blank? end end def to_s() @col end def to_sym() to_s.to_sym end def inspect() "#{@col}[@#{@as}]" end def to_alias() "#{@col}[#{@as}]" end def == other other = Col.new other @col == other.col && @as == other.as end def === other @col == Col.new( other).col end end def initialize model, *col @model = model @last_model = nil *@path, @col = *Array.wrap( col). collect {|x| x.to_s.split /[.\/]/ }. flatten. collect( &Col.method( :new)). reject {|x| x === :self } end def last_model @last_model ||= each{} end def each model = @model @path.each do |rel| unless rel === :self model = SmqlToAR.model_of model, rel return false unless model yield rel, model end end model end def exist_in? model = last_model return false unless model model.column_names.include? @col.to_s end def protected? model = @model each do |rel, _model| pr = Array.wrap model.respond_to?( :smql_protected) ? model.smql_protected : nil pr.include? rel.to_s model = _model end pr = Array.wrap model.respond_to?( :smql_protected) ? model.smql_protected : nil pr.include? @col.to_s end def joins builder = nil, table = nil, &exe pp = [] table = Array.wrap table exe ||= builder ? lambda {|j, m| builder.joins table+j, m} : Array.method( :[]) collect do |rel, model| pp.push rel exe.call pp, model end end def self?() !@col end def length() @path.length+(self.self? ? 0 : 1) end def size() @path.size+(self.self? ? 0 : 1) end def to_a() @path+(self.self? ? [] : [@col]) end def to_s() to_a.join '.' end def to_sym() to_s.to_sym end def to_json() to_s end def inspect() "#" end def relation() self.self? ? model : SmqlToAR.model_of( last_model, @col) end def allowed?() ! self.protected? end def child?() @path.empty? and !!relation end end attr_reader :model, :query, :conditions, :builder, :order attr_accessor :logger if defined? Rails class Railtie < ::Rails::Railtie initializer "active_record.logger" do SmqlToAR.logger = ::Rails.logger end end end @@logger = ::Rails.logger || begin require 'logger'; Logger.new( $stdout) end class <