lib/engine2/core.rb in engine2-1.0.4 vs lib/engine2/core.rb in engine2-1.0.5

- old
+ new

@@ -1,6 +1,7 @@ -#coding: utf-8 +# coding: utf-8 +# frozen_string_literal: true module PrettyJSON def to_json_pretty JSON.pretty_generate(self) end @@ -11,32 +12,50 @@ # super to_s('f') end end +class Sequel::SQL::QualifiedIdentifier + def to_json(*) + "\"#{table}__#{column}\"" + end + + def to_sym + :"#{table}__#{column}" + end +end + class Object def instance_variables_hash - instance_variables.inject({}) do |h, i| + instance_variables.reduce({}) do |h, i| h[i] = instance_variable_get(i) h end end end class Proc def to_json(*) loc = source_location - "\"#<Proc:#{loc.first[/\w+.rb/]}:#{loc.last}>\"" + loc ? "\"#<Proc:#{loc.first[/\w+.rb/]}:#{loc.last}>\"" : '"source unknown"' end def chain &blk - proc = self + prc = self lambda do |obj| - obj.instance_eval(&proc) + obj.instance_eval(&prc) obj.instance_eval(&blk) end end + + def chain_args &blk + prc = self + lambda do |*args| + instance_exec(*args, &prc) + instance_exec(*args, &blk) + end + end end class Hash include PrettyJSON @@ -109,31 +128,51 @@ end end class Symbol def icon - "<span class='glyphicon glyphicon-#{self}'></span>" + s = self + if s[0, 3] == 'fa_' + "<i class='fa fa-#{s[3 .. -1]}'></i>" + else + "<span class='glyphicon glyphicon-#{s}'></span>" + end end - def aicon - "<i class='fa fa-#{self}'></i>" + def q col + col.qualify self end end +module Faye + class WebSocket + module API + def send! msg + msg = msg.to_json if msg.is_a? Hash + send msg + end + end + end +end + class << Sequel attr_accessor :alias_columns_in_joins def split_keys id id.split(Engine2::SETTINGS[:key_separator]) end + + def join_keys keys + keys.join(Engine2::SETTINGS[:key_separator]) + end end class Sequel::Database attr_accessor :models, :default_schema def cache_file - "#{Engine2::app}/#{opts[:orig_opts][:name]}.dump" + "#{Engine2::SETTINGS.path_for(:db_path)}/#{opts[:orig_opts][:name]}.dump" end def load_schema_cache_from_file self.models = {} load_schema_cache? cache_file if adapter_scheme @@ -142,11 +181,10 @@ def dump_schema_cache_to_file dump_schema_cache? cache_file if adapter_scheme end end -Sequel.quote_identifiers = false Sequel.extension :core_extensions Sequel::Inflections.clear Sequel.alias_columns_in_joins = true # Sequel::Model.plugin :json_serializer, :naked => true # Sequel::Model.plugin :timestamps @@ -275,11 +313,11 @@ key.is_a?(Array) ? key : [key] end def primary_keys_qualified # cache it ? - primary_keys.map{|k|k.qualify(table_name)} + primary_keys.map{|k|table_name.q(k)} end def primary_keys_hash id Hash[primary_keys.zip(id)] end @@ -288,21 +326,20 @@ Hash[primary_keys_qualified.zip(id)] end end module DatasetMethods - def ensure_primary_key - pk = @model.primary_keys + pk = model.primary_keys raise Engine2::E2Error.new("No primary key defined for model #{model}") unless pk && pk.all? if opts_select = @opts[:select] sel_pk = [] opts_select.each do |sel| name = case sel when Symbol - sel.to_s =~ /\w+__(\w+)/ ? $1.to_sym : sel + sel when Sequel::SQL::QualifiedIdentifier sel.column when Sequel::SQL::AliasedExpression sel # nil #sel.aliaz # ? @@ -312,98 +349,89 @@ end if pk.length == sel_pk.length self else - sels = (pk - sel_pk).map{|k| k.qualify(@model.table_name)} + sels = (pk - sel_pk).map{|k| model.table_name.q(k)} select_more(*sels) end else - select(*pk.map{|k| k.qualify(@model.table_name)}) + select(*pk.map{|k| model.table_name.q(k)}) end end + def extract_select sel, al = nil, &blk + case sel + when Symbol + yield nil, sel, nil + when Sequel::SQL::QualifiedIdentifier + yield sel.table, sel.column, al + when Sequel::SQL::AliasedExpression, Sequel::SQL::Function + sel + # extract_select sel.expression, sel.aliaz, &blk + # expr = sel.expression + # yield expr.table, expr.column + else + raise Engine2::E2Error.new("Unknown selection #{sel}") + end + end + def setup! fields joins = {} type_info = model.type_info model_table_name = model.table_name - @opts[:select].map! do |sel| + @opts[:select] = @opts[:select].map do |sel| extract_select sel do |table, name, aliaz| - if table + info = if table if table == model_table_name - m = model + model else - a = model.many_to_one_associations[table] # || model.one_to_one_associations[table] - raise Engine2::E2Error.new("Association #{table} not found for model #{model}") unless a - m = a.associated_class - end - # raise Engine2::E2Error.new("Model not found for table #{table} in model #{model}") unless m - info = m.type_info + assoc = model.many_to_one_associations[table] || model.many_to_many_associations[table] + raise Engine2::E2Error.new("Association #{table} not found for model #{model}") unless assoc + assoc.associated_class + end.type_info else - info = type_info + type_info end - f_info = info[name] - raise Engine2::E2Error.new("Column #{name} not found for table #{table || model_table_name}") unless f_info - table ||= model_table_name - if table == model_table_name fields << name else - fields << :"#{table}__#{name}" - joins[table] = model.many_to_one_associations[table] + fields << table.q(name) + joins[table] ||= model.many_to_one_associations[table] || model.many_to_many_associations[table] end + f_info = info[name] + raise Engine2::E2Error.new("Column #{name} not found for table #{table || model_table_name}") unless f_info if f_info[:dummy] nil - # elsif f_info[:type] == :blob_store - # # (~{name => nil}).as :name - # # Sequel.char_length(name).as name - # nil else + qname = table.q(name) if table != model_table_name - if Sequel.alias_columns_in_joins - name.qualify(table).as(:"#{table}__#{name}") - else - name.qualify(table) - end + Sequel.alias_columns_in_joins ? qname.as(:"#{table}__#{name}") : qname else - name.qualify(table) + qname end end end end - @opts[:select].compact! + @opts[:select].compact!.freeze joins.reduce(self) do |joined, (table, assoc)| m = assoc.associated_class - keys = assoc[:qualified_key] - joined.left_join(table, m.primary_keys.zip(keys.is_a?(Array) ? keys : [keys])) - end - end - - def extract_select sel, al = nil, &blk - case sel - when Symbol - if sel.to_s =~ /^(\w+)__(\w+?)(?:___(\w+))?$/ - yield $1.to_sym, $2.to_sym, $3 ? $3.to_sym : nil - else - yield nil, sel, al + case assoc[:type] + when :many_to_one + keys = assoc[:qualified_key] + joined.left_join(table, m.primary_keys.zip(keys.is_a?(Array) ? keys : [keys])) + when :many_to_many + joined.left_join(assoc[:join_table], assoc[:left_keys].zip(model.primary_keys)).left_join(m.table_name, m.primary_keys.zip(assoc[:right_keys])) + else unsupported_association end - when Sequel::SQL::QualifiedIdentifier - yield sel.table, sel.column, al - when Sequel::SQL::AliasedExpression, Sequel::SQL::Function - sel - # extract_select sel.expression, sel.aliaz, &blk - # expr = sel.expression - # yield expr.table, expr.column - else - raise Engine2::E2Error.new("Unknown selection #{sel}") end end def get_opts @opts @@ -431,14 +459,25 @@ end module Engine2 LOCS ||= Hash.new{|h, k| ":#{k}:"} PATH ||= File.expand_path('../..', File.dirname(__FILE__)) - SETTINGS ||= {key_separator: '|'} + SETTINGS ||= { + key_separator: '|', + app_path: 'app', + db_path: 'db', + model_path: 'models', + view_path: 'views', + asset_path: 'assets', + conf_path: 'conf' + } + def SETTINGS.path_for path + "#{self[:app_path]}/#{self[path]}" + end unless SETTINGS.frozen? + class << self - attr_reader :app attr_reader :core_loaded def database name Object.const_set(name, yield) unless Object.const_defined?(name) end @@ -455,47 +494,51 @@ def model_boot &blk @model_boot_blk = blk end def bootstrap_e2db - e2_db_file = (defined? JRUBY_VERSION) ? "jdbc:sqlite:#{@app}/engine2.db" : "sqlite://#{@app}/engine2.db" - const_set :E2DB, connect(e2_db_file, loggers: [Logger.new($stdout)], convert_types: false, name: :engine2) + e2_db_path = "#{Engine2::SETTINGS.path_for(:db_path)}/engine2.db" + e2_db_url = (defined? JRUBY_VERSION) ? "jdbc:sqlite:#{e2_db_path}" : "sqlite://#{e2_db_path}" + const_set :E2DB, connect(e2_db_url, loggers: [Logger.new($stdout)], convert_types: false, name: :engine2) const_set :DUMMYDB, Sequel::Database.new(uri: 'dummy') def DUMMYDB.synchronize *args;end end def reload @core_loaded = true t = Time.now - Action.count = 0 + ActionNode.count = 0 SCHEMES.user.clear Sequel::DATABASES.each do |db| db.models.each{|n, m| Object.send(:remove_const, n) if Object.const_defined?(n)} unless db == E2DB || db == DUMMYDB end - load "#{app}/boot.rb" + load "#{Engine2::SETTINGS[:app_path]}/boot.rb" Sequel::DATABASES.each &:load_schema_cache_from_file @model_boot_blk.() if @model_boot_blk load 'engine2/models/Files.rb' load 'engine2/models/UserInfo.rb' - Dir["#{app}/models/*"].each{|m| load m} + Dir["#{Engine2::SETTINGS.path_for(:model_path)}/*"].each{|m| load m} puts "MODELS: #{Sequel::DATABASES.reduce(0){|s, d|s + d.models.size}}, Time: #{Time.now - t}" Sequel::DATABASES.each &:dump_schema_cache_to_file send(:remove_const, :ROOT) if defined? ROOT - const_set(:ROOT, Action.new(nil, :api, RootMeta, {})) + const_set(:ROOT, ActionNode.new(nil, :api, RootAction, {})) @boot_blk.(ROOT) - ROOT.setup_action_tree - puts "BOOTSTRAP #{app}, Time: #{Time.new - t}" + ROOT.setup_node_tree + puts "BOOTSTRAP #{Engine2::SETTINGS[:name]}, Time: #{Time.new - t}" end - def bootstrap app, settings = {} - @app = app + def bootstrap path, settings = {} SETTINGS.merge! settings - SETTINGS[:name] ||= File::basename(app) + SETTINGS[:path] = path + SETTINGS[:name] ||= File::basename(path) + SETTINGS.freeze + Handler.set :public_folder, "public" + Handler.set :views, [SETTINGS.path_for(:view_path), "#{Engine2::PATH}/views"] bootstrap_e2db require 'engine2/pre_bootstrap' reload require 'engine2/post_bootstrap'