visual_query 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +59 -0
  4. data/Rakefile +23 -0
  5. data/app/assets/images/visual_query/ajax-loader.gif +0 -0
  6. data/app/assets/javascripts/visual_query/index.js +288 -0
  7. data/app/assets/stylesheets/visual_query/visual_query.css +161 -0
  8. data/app/controllers/queries_controller.rb +157 -0
  9. data/app/helpers/application_helper.rb +5 -0
  10. data/app/helpers/queries_helper.rb +43 -0
  11. data/app/views/queries/_columns.html.erb +5 -0
  12. data/app/views/queries/_command.html.erb +5 -0
  13. data/app/views/queries/_command_results_browser.html.erb +1 -0
  14. data/app/views/queries/_command_save.html.erb +1 -0
  15. data/app/views/queries/_commands_results.html.erb +2 -0
  16. data/app/views/queries/_filter.html.erb +6 -0
  17. data/app/views/queries/_list_all_relations.html.erb +8 -0
  18. data/app/views/queries/_list_joinable_relations.html.erb +10 -0
  19. data/app/views/queries/_list_relation.html.erb +8 -0
  20. data/app/views/queries/_name.html.erb +8 -0
  21. data/app/views/queries/_results.html.erb +14 -0
  22. data/app/views/queries/_results_column_filter.html.erb +3 -0
  23. data/app/views/queries/_results_column_hide.html.erb +3 -0
  24. data/app/views/queries/_results_column_human_name.html.erb +8 -0
  25. data/app/views/queries/_results_empty.html.erb +3 -0
  26. data/app/views/queries/_sort.html.erb +12 -0
  27. data/app/views/queries/_sort_condition.html.erb +18 -0
  28. data/app/views/queries/_url_root.html.erb +1 -0
  29. data/app/views/queries/_warning_large_result_set.html.erb +12 -0
  30. data/app/views/queries/filters/_boolean.html.erb +6 -0
  31. data/app/views/queries/filters/_date.html.erb +17 -0
  32. data/app/views/queries/filters/_datetime.html.erb +1 -0
  33. data/app/views/queries/filters/_decimal.html.erb +1 -0
  34. data/app/views/queries/filters/_integer.html.erb +1 -0
  35. data/app/views/queries/filters/_numeric.html.erb +6 -0
  36. data/app/views/queries/filters/_string.html.erb +1 -0
  37. data/app/views/queries/filters/_text.html.erb +6 -0
  38. data/app/views/queries/index.html.erb +39 -0
  39. data/app/views/queries/new.html.erb +27 -0
  40. data/app/views/queries/not_found.html.erb +1 -0
  41. data/app/views/queries/show.html.erb +40 -0
  42. data/app/views/queries/sql_form.html.erb +16 -0
  43. data/config/initializers/visual_query.rb +11 -0
  44. data/config/routes.rb +26 -0
  45. data/db/migrate/20130927090319_create_visual_query_schema.rb +9 -0
  46. data/db/migrate/20130927090400_create_visual_query_metadata_table.rb +16 -0
  47. data/lib/tasks/visual_query_tasks.rake +12 -0
  48. data/lib/tutuf/visual_query.rb +6 -0
  49. data/lib/tutuf/visual_query/base.rb +268 -0
  50. data/lib/tutuf/visual_query/common.rb +58 -0
  51. data/lib/tutuf/visual_query/metadata.rb +35 -0
  52. data/lib/tutuf/visual_query/single.rb +111 -0
  53. data/lib/tutuf/visual_query/sql.rb +20 -0
  54. data/lib/visual_query.rb +4 -0
  55. data/lib/visual_query/engine.rb +4 -0
  56. data/lib/visual_query/version.rb +3 -0
  57. data/test/dummy/README.rdoc +261 -0
  58. data/test/dummy/Rakefile +7 -0
  59. data/test/dummy/app/assets/javascripts/application.js +15 -0
  60. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  61. data/test/dummy/app/controllers/application_controller.rb +3 -0
  62. data/test/dummy/app/helpers/application_helper.rb +2 -0
  63. data/test/dummy/app/models/account.rb +3 -0
  64. data/test/dummy/app/models/address.rb +3 -0
  65. data/test/dummy/app/models/category.rb +3 -0
  66. data/test/dummy/app/models/composite_pk.rb +3 -0
  67. data/test/dummy/app/models/customer.rb +4 -0
  68. data/test/dummy/app/models/order.rb +4 -0
  69. data/test/dummy/app/models/person.rb +4 -0
  70. data/test/dummy/app/models/post.rb +3 -0
  71. data/test/dummy/app/models/product.rb +4 -0
  72. data/test/dummy/app/models/product_in.rb +3 -0
  73. data/test/dummy/app/models/product_out.rb +3 -0
  74. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  75. data/test/dummy/config.ru +4 -0
  76. data/test/dummy/config/application.rb +53 -0
  77. data/test/dummy/config/boot.rb +10 -0
  78. data/test/dummy/config/database.yml +12 -0
  79. data/test/dummy/config/environment.rb +5 -0
  80. data/test/dummy/config/environments/development.rb +29 -0
  81. data/test/dummy/config/environments/production.rb +65 -0
  82. data/test/dummy/config/environments/test.rb +33 -0
  83. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  84. data/test/dummy/config/initializers/inflections.rb +15 -0
  85. data/test/dummy/config/initializers/mime_types.rb +5 -0
  86. data/test/dummy/config/initializers/secret_token.rb +7 -0
  87. data/test/dummy/config/initializers/session_store.rb +8 -0
  88. data/test/dummy/config/initializers/visual_query.rb +1 -0
  89. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  90. data/test/dummy/config/locales/en.yml +5 -0
  91. data/test/dummy/config/routes.rb +3 -0
  92. data/test/dummy/db/migrate/20130927112446_create_tables.rb +77 -0
  93. data/test/dummy/db/structure.sql +651 -0
  94. data/test/dummy/log/development.log +1548 -0
  95. data/test/dummy/log/test.log +36575 -0
  96. data/test/dummy/public/404.html +26 -0
  97. data/test/dummy/public/422.html +26 -0
  98. data/test/dummy/public/500.html +25 -0
  99. data/test/dummy/public/favicon.ico +0 -0
  100. data/test/dummy/script/rails +6 -0
  101. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/-h/-hj2e_RSTanfbfrP0tso5Q7actRM6_clE5hetFlQ2y8.cache +1 -0
  102. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/3B/3B_zqoNDfkO8wvAME66zxm9KzQaeDVSjnH0qC08yufM.cache +1 -0
  103. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/5g/5g7dhxVp4YbZmFw_-T3aU2oYq2Z9Jgtps0CKneXYSS0.cache +2 -0
  104. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/6-/6-CtZO6uG0yfwU8-098Sdy2wnfO0W6DbFu6B6DKYuiw.cache +1 -0
  105. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/8k/8kFIVN4cTS9KCQt_UIF5s_rcY-bMYlQpM489D98hvP4.cache +1 -0
  106. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Au/AukU7t3xLnyCh7qW4u45q9YFmjVcYujmIFbnaOhF4Mo.cache +1 -0
  107. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Bm/Bmsq0vYQqlrtfq5s-W8kcfLvwcsPT_6_5XxXt9J_QOw.cache +1 -0
  108. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/CQ/CQLNg7a2TsUWgc7JXjDkjseMig_dPVm6AvqO2IWk5zg.cache +0 -0
  109. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/FX/FX9ZXN3HEHR5hPzvxW8rakWEt2ot4IPJyDB67O7KPZk.cache +2 -0
  110. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Hv/HvOStITEkFHlcJCgaDnND6wzPw4dMmdAdZB1Xm6JfSA.cache +0 -0
  111. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/NW/NWCtuFzfIgixavqY71NIAa4ajbsXxRuiLNjceHgQ24M.cache +2 -0
  112. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Ta/TaG641Ow168nkagg10mh6zuWS8RwWuawpHuMGakCVjU.cache +0 -0
  113. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/VN/VNCapNKJLeponthNeFJhaBYs92UBT3P8PugENHP0474.cache +2 -0
  114. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/VR/VRi0Hz7tc62H5Of9XVjyAk7vSNmMr8xeYowo6lSBnZg.cache +2 -0
  115. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Vg/Vgl_u6t3BvczgGi_ZJlyyo7xYSe-GgEshLofx-3QorI.cache +1 -0
  116. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Vq/VqGJMF3Cpvp3fw2IEIkE2tzdFo_OdcEmxN58BQwbVDY.cache +0 -0
  117. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/W6/W6DnXCIMHJ2i5hUkEiNeDLroWxW3VU18nq292n5jlts.cache +0 -0
  118. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Z7/Z7fH8ST-O3GMnDUKvtKHHTSObfH2Nbs0J1QS79i80yE.cache +0 -0
  119. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Zh/ZhrmuPgfbHthzikN8QSHR0Q0bLtSYS1Bzl9HauWeDfU.cache +0 -0
  120. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/bY/bYzYCY_bAGQGVGMbcxtKIhUYrgDQhmQVTmK50JrMNb8.cache +0 -0
  121. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/eT/eTmCDSnc2m9ER5Cn85g84xyTkVLWKLbbwPFQo8eUAIg.cache +0 -0
  122. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/h-/h-taE7cHlbq76GUb5kHenjih_y66mO0w0lIZs7vY-0s.cache +1 -0
  123. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/ji/jikmiWyu-cXN_ZJ4hgLc3kuCAY-QJY2jmPeXS4_9vZY.cache +0 -0
  124. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/kC/kCijps52gsNlkYgT2Qzdz9UcSaxhcMGNfNL7MIiWk54.cache +1 -0
  125. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/kQ/kQt-OsUDJg_sl1be-FqJ6Vhw4XVguw9_msZEwXP0Nh0.cache +0 -0
  126. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/nd/ndbe-ZZWBqU5gLx5nxauCFvv963Zm3xqVEwVYQ7X_X8.cache +1 -0
  127. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/q6/q6BYa32YJF11eGVapO4ouNl6gayPIsARgMavlzZmoi0.cache +0 -0
  128. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/sm/sm7AdmddDYbFx4-eo_y_kaZspanmc-jiJeM8j2DXX5k.cache +1 -0
  129. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/ux/uxPH9lLOW42lEQxJXnBizEObZReD8qkz6Eb6fdS6Ur4.cache +1 -0
  130. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/wn/wn0ayyM-chRdwEPI11SLkFT-7G2-GcOX8UIIC2kOWLY.cache +1 -0
  131. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/x7/x7KkTV3ibfIEysLB_ug5bfmnn2VLV_BldukPR3EoPBk.cache +0 -0
  132. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/xK/xKo0PfDcZuMh8oO-6Gr4j4S8eR2qUNY9Gau4kAxKIH0.cache +1 -0
  133. data/test/fixtures/accounts.yml +4 -0
  134. data/test/fixtures/categories.yml +3 -0
  135. data/test/fixtures/categories_posts.yml +3 -0
  136. data/test/fixtures/people.yml +8 -0
  137. data/test/fixtures/posts.yml +8 -0
  138. data/test/fixtures/product_ins.yml +15 -0
  139. data/test/fixtures/product_outs.yml +5 -0
  140. data/test/fixtures/products.yml +6 -0
  141. data/test/fixtures/saved_queries.rb +7 -0
  142. data/test/functional/queries_controller_test.rb +267 -0
  143. data/test/functional/routes_test.rb +111 -0
  144. data/test/integration/navigation_test.rb +10 -0
  145. data/test/test_helper.rb +15 -0
  146. data/test/unit/metadata_test.rb +10 -0
  147. data/test/unit/sql_test.rb +37 -0
  148. data/test/unit/visual_query_test.rb +648 -0
  149. metadata +381 -0
@@ -0,0 +1,27 @@
1
+ <%= render 'url_root' %>
2
+ <span class="strong"><%=_('Таблици:')%></span>
3
+ <div id="relations_container">
4
+ <%= render :partial => 'list_all_relations' %>
5
+ </div>
6
+ <%= form_tag(visual_query.results_queries_path(''), :method=> "post", :id => "query") do -%>
7
+ <div id="joined_relations_container">
8
+ <span class="strong"><%=_('Таблици в справката:') %></span>
9
+ <ul class="relation_path">
10
+ <li>
11
+ <input type="hidden" name="rows[]" value="0"/>
12
+ <input type="button" name="add_balanced_query" value="<%= _("Б") %>" onclick="Tutuf.VisualQuery.add_balanced_query()" />
13
+ </li>
14
+ </ul>
15
+ </div>
16
+ <%= render :partial => 'sort' %>
17
+ <%= render :partial => 'command' %>
18
+ <span class="strong"><%=_('Колони:')%></span>
19
+ <table id="results">
20
+ <thead>
21
+ <tr id="hide"></tr>
22
+ <tr id="filter"></tr>
23
+ <tr id="relation_column"></tr>
24
+ </thead>
25
+ <tbody/>
26
+ </table>
27
+ <% end %>
@@ -0,0 +1 @@
1
+ Няма справка с име <strong><%= params[:name] %></strong>
@@ -0,0 +1,40 @@
1
+ <%= render 'url_root' %>
2
+ <%= form_tag(visual_query.results_queries_path(@query.name), :method=> "post", :id => "query") do -%>
3
+ <div id="command">
4
+ <h1><%= @query.name %></h1>
5
+ <input id="name" type="hidden" name="name" value="<%= @query.name %>"/>
6
+ <%= render :partial => 'sort' %>
7
+ <%= render :partial => 'commands_results' %>
8
+ </div>
9
+ <input type="hidden" name="relations[][schema]" value="<%= @query.schema %>"/>
10
+ <input type="hidden" name="relations[][rel_name]" value="<%= @query.name %>"/>
11
+ <span class="strong"><%=_('Колони:')%></span>
12
+ <table id="results">
13
+ <thead>
14
+ <tr id="hide">
15
+ <%- for column in @query.columns do -%>
16
+ <%= render :partial => 'results_column_hide' %>
17
+ <%- end -%>
18
+ </tr>
19
+ <tr id="filter">
20
+ <%- for column in @query.columns do -%>
21
+ <%= render :partial => 'results_column_filter', :locals => {:column => {:schema => @query.schema,
22
+ :rel_name => @query.name,
23
+ :col_name => column[:col_name] }} %>
24
+ <%- end -%>
25
+ </tr>
26
+ <tr id="relation_column">
27
+ <%- for column in @query.columns do -%>
28
+ <th class="relation_column">
29
+ <%= _(column[:col_name]) %>
30
+ <%- for param_name in ['columns', 'all_columns'] do-%>
31
+ <input type="hidden" name="<%= param_name %>[][rel_name]" value="<%= column[:rel_name] %>"/>
32
+ <input type="hidden" name="<%= param_name %>[][col_name]" value="<%= column[:col_name] %>"/>
33
+ <%- end -%>
34
+ </th>
35
+ <%- end -%>
36
+ </tr>
37
+ </thead>
38
+ <tbody/>
39
+ </table>
40
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <%= render 'url_root' %>
2
+ <%= form_tag(visual_query.results_queries_path(@query.nil? ? '' : @query.name), :method=> "get", :id => "query") do -%>
3
+ <%= text_area_tag :sql, @sql, :rows => 20, :cols => 120 %>
4
+ <%= render :partial => 'command_results_browser' %>
5
+ <% if controller.action_name == "edit_sql" -%>
6
+ <%= hidden_field_tag 'name', @query.name %>
7
+ <input type="button" name="update" value="<%= _("Запис") %>" onclick="Tutuf.VisualQuery.update_sql()" />
8
+ <% else -%>
9
+ <%= render :partial => 'name' %>
10
+ <%= render :partial => 'command_save' %>
11
+ <% end -%>
12
+ <span class="notice"></span>
13
+ <table id="results">
14
+ <tbody/>
15
+ </table>
16
+ <% end %>
@@ -0,0 +1,11 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module VisualQuery::PseudoGettext
3
+ def _(text) text end
4
+ def n_(singular,plural,arity) singular end
5
+ end
6
+
7
+ Object.class_eval do
8
+ include VisualQuery::PseudoGettext unless defined?(_) || defined?(n_)
9
+ end
10
+
11
+ require 'tutuf/visual_query'
data/config/routes.rb ADDED
@@ -0,0 +1,26 @@
1
+ # encode: UTF-8
2
+ VisualQuery::Engine.routes.draw do
3
+ resources :queries, path: '', only: [:index, :create] do
4
+ collection do
5
+ get '/new' => 'queries#new'
6
+
7
+ get '/columns/:klass' => 'queries#columns'
8
+ get '/list_joinable/:klass' => 'queries#list_joinable'
9
+ get '/join_data/:klass' => 'queries#join_data'
10
+
11
+ get :filter
12
+ get :remove_filter
13
+ get :sort_condition
14
+
15
+ get :new_sql
16
+ get '/edit_sql/:name' => 'queries#edit_sql', as: 'edit_sql'
17
+ put '/update_sql/:name' => 'queries#update_sql'
18
+
19
+ # some queries have too many params to fit in the server's meagre buffer for GETs
20
+ match '/results/:name' => 'queries#results', as: 'results', via: ['get','post']
21
+
22
+ get '/:name' => 'queries#show', as: 'show'
23
+ delete '/:name' => 'queries#destroy'
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ class CreateVisualQuerySchema < ActiveRecord::Migration
2
+ def up
3
+ execute %{CREATE SCHEMA "tutuf::visual_query"}
4
+ end
5
+
6
+ def down
7
+ execute %{DROP SCHEMA "tutuf::visual_query" CASCADE}
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ class CreateVisualQueryMetadataTable < ActiveRecord::Migration
2
+ def up
3
+ create_table :tutuf_visual_query_metadata do |t|
4
+ t.string :name, :limit => 63, :null => false
5
+ t.text :params
6
+ t.text :order_by
7
+ t.timestamp :created_at
8
+ end
9
+
10
+ add_index :tutuf_visual_query_metadata, :name, :unique => true
11
+ end
12
+
13
+ def down
14
+ drop_table :tutuf_visual_query_metadata
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+ namespace :visual_query do
3
+ desc "Drops all query views, but leaves metadata"
4
+ task :disable => :environment do
5
+ Tutuf::VisualQuery::Base.disable
6
+ end
7
+
8
+ desc "Recreates all query views from metadata"
9
+ task :enable => :environment do
10
+ Tutuf::VisualQuery::Base.enable
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'tutuf/visual_query/common'
3
+ require 'tutuf/visual_query/single'
4
+ require 'tutuf/visual_query/base'
5
+ require 'tutuf/visual_query/metadata'
6
+ require 'tutuf/visual_query/sql'
@@ -0,0 +1,268 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Tutuf
3
+ module VisualQuery
4
+ class Base < Common
5
+ class << self
6
+
7
+ # PostgreSQL schema in which the saved queries live
8
+ def schema
9
+ "tutuf::visual_query"
10
+ end
11
+
12
+ # All direct ascendants of ActiveRecord::Base
13
+ def klasses
14
+ return @@klasses if defined?(@@klasses)
15
+ @@klasses = []
16
+ all_classes = model_filenames.map do |model_filename|
17
+ klass = model_filename.gsub(/\.rb$/, '').split(%r{/}).map { |c| c.camelize }.join('::')
18
+ klass.constantize
19
+ end
20
+ all_classes.each do |klass|
21
+ @@klasses << klass if klass.respond_to?(:superclass) && klass.superclass == ActiveRecord::Base
22
+ end
23
+ @@klasses.uniq
24
+ end
25
+
26
+ # List of all relations that the +klass+ can be joined with. Currently self joins are not supported.
27
+ def joinable_relations(klass)
28
+ klass.reflections.values.reject{ |reflection| reflection.options[:polymorphic] || reflection.klass == klass }
29
+ end
30
+
31
+ def columns(klass)
32
+ inheritance_column = klass.columns.detect{|c|c.name == klass.inheritance_column}
33
+ col_names = (klass.primary_key.is_a?(Array) ? klass.primary_key : [klass.primary_key]) + klass.content_columns.collect{ |col| col.name} + (inheritance_column ? [inheritance_column.name] : [])
34
+ col_names.map(&:to_s)
35
+ end
36
+
37
+ # Array of relations to be joinded
38
+ def join_relations(reflection)
39
+ if reflection.macro == :has_and_belongs_to_many
40
+ [reflection.join_table, reflection.table_name]
41
+ else
42
+ [reflection.klass.table_name]
43
+ end
44
+ end
45
+
46
+ # Join condition between +reflection+ (ActiveRecord::Reflection::MacroReflection) and its +klass+ in format
47
+ # [[{'rel_name' => reflection relation, 'col_name' => reflection column},
48
+ # {'rel_name' => klass relation, 'col_name' => klass column}, ... ]]
49
+ # The order of relations in the array is important for parsing afterwards
50
+ def join_condition(reflection)
51
+ case reflection.macro
52
+ when :belongs_to
53
+ [[{'rel_name' => reflection.active_record.table_name, 'col_name' => reflection.foreign_key},
54
+ {'rel_name' => reflection.table_name, 'col_name' => reflection.klass.primary_key}]]
55
+ when :has_many, :has_one
56
+ [[{'rel_name' => reflection.active_record.table_name, 'col_name' => reflection.active_record.primary_key},
57
+ {'rel_name' => reflection.table_name, 'col_name' => reflection.foreign_key}]]
58
+ when :has_and_belongs_to_many
59
+ [[ {'rel_name' => reflection.active_record.table_name, 'col_name' => reflection.active_record.primary_key},
60
+ {'rel_name' => reflection.join_table, 'col_name' => reflection.foreign_key}],
61
+ [ {'rel_name' => reflection.join_table, 'col_name' => reflection.association_foreign_key},
62
+ {'rel_name' => reflection.klass.table_name, 'col_name' => reflection.klass.primary_key}]]
63
+ end
64
+ end
65
+
66
+ def data_type(params)
67
+ qualified_relation_name = quote_relation_name('schema' => params['schema'], 'rel_name' => params['rel_name'])
68
+ res = connection.columns(qualified_relation_name).find{|c| c.name == params['col_name']}
69
+ if res
70
+ res.type
71
+ else
72
+ nil
73
+ end
74
+ end
75
+
76
+ # Returns array of metadata for all queries (Tutuf::VisualQuery::Metadata)
77
+ def find_all
78
+ Metadata.all
79
+ end
80
+
81
+ def find_by_name(name)
82
+ if metadata = Metadata.find_by_name(name)
83
+ object = new(metadata.params)
84
+ object.instance_variable_set("@metadata", metadata)
85
+ object
86
+ end
87
+ end
88
+
89
+ def disable
90
+ connection.execute "DROP SCHEMA IF EXISTS #{quote_ident(schema)} CASCADE"
91
+ end
92
+
93
+ def enable
94
+ connection.execute "CREATE SCHEMA #{quote_ident(schema)}"
95
+ find_all.each do |metadata|
96
+ vq = new(metadata.params.merge('metadata' => metadata))
97
+ vq.save
98
+ end
99
+ end
100
+
101
+ private
102
+ def model_filenames
103
+ Dir.chdir("#{Rails.root.to_s}/app/models"){ Dir["**/*.rb"] }
104
+ end
105
+ end #end class methods
106
+
107
+ def initialize(params)
108
+ # params were kept in array in an older version
109
+ params = {} if params.blank? || Array === params
110
+ @relations = params['relations']
111
+ rows = (params['rows'].blank? ? [0] : params['rows'])
112
+ if balanced = (rows.size > 1)
113
+ @single_queries = rows.map do |row|
114
+ relations = params['relations'].select{|h| h['row'] == row} if params['relations']
115
+ join_conditions = params['join_conditions'].select{|jc| jc['row'] == row} if params['join_conditions']
116
+ Tutuf::VisualQuery::Single.new({'action' => params['action'],
117
+ 'relations' => relations,
118
+ 'join_conditions' => join_conditions,
119
+ 'all_columns' => params['all_columns'],
120
+ 'all_selected_columns' => params['columns'],
121
+ 'sort_conditions' => params['sort_conditions'],
122
+ 'balanced' => balanced}.merge(%w[columns filters].inject({}){|res,key| res[key] = select(relations,params[key]); res}))
123
+ end
124
+ elsif params['sql']
125
+ @single_queries = [Tutuf::VisualQuery::Sql.new(params)]
126
+ else
127
+ @single_queries = [Tutuf::VisualQuery::Single.new(params)]
128
+ end
129
+
130
+ name = params['query']['name'] if params['query']
131
+ @metadata = params.delete('metadata') || Metadata.new(:name => name, :params => params)
132
+ end
133
+
134
+ attr_reader :single_queries, :relations
135
+
136
+ def sql?
137
+ single_queries.size == 1 && Tutuf::VisualQuery::Sql === single_queries.first
138
+ end
139
+
140
+ def filters
141
+ single_queries.map{|sq| sq.filters.nil? ? [] : sq.filters}.flatten
142
+ end
143
+
144
+ def query
145
+ single_queries.size > 1 ? single_queries.map{|sq| "(#{sq.query})"}.join(" UNION ALL ") : single_queries.first.query
146
+ end
147
+
148
+ private
149
+ def select(relations,arr)
150
+ return if relations.blank? || arr.blank?
151
+ relations = relations.map{|h| h['rel_name']}
152
+ arr.select{|h| relations.member?(h['rel_name'])}
153
+ end
154
+
155
+ def results(headers=false)
156
+ return [] if(!sql? && (relations.nil? || relations == ""))
157
+ @results = []
158
+ res = connection.execute(query)
159
+ @results << res.fields if headers
160
+ @results += res.values
161
+ res.clear
162
+ @results
163
+ end
164
+
165
+ public
166
+
167
+ def to_a(headers=false)
168
+ results(headers)
169
+ end
170
+
171
+ def to_csv
172
+ data = results(true)
173
+ CSV.generate { |csv| data.each { |row| csv << row } }
174
+ end
175
+
176
+ def to_json
177
+ return [] if(!sql? && (relations.nil? || relations == ""))
178
+ JSON.generate(connection.select_all(query))
179
+ end
180
+
181
+ def schema
182
+ Tutuf::VisualQuery::Base.schema
183
+ end
184
+
185
+ def name
186
+ @metadata.name
187
+ end
188
+
189
+ def errors
190
+ @metadata.errors
191
+ end
192
+
193
+ def valid?
194
+ @metadata.valid?
195
+ end
196
+
197
+ def new?
198
+ @metadata.new_record?
199
+ end
200
+
201
+ def max_result_length
202
+ 20000
203
+ end
204
+
205
+ def large_result_set?
206
+ @results && !@results.empty? && (@results.length * @results[0].length > max_result_length)
207
+ end
208
+
209
+ # Array of hashes representing qualified column names that look like { 'rel_name' => "some relation", 'col_name' => "some column"}.
210
+ # Raises an exception if the query has a name and is not saved yet. Returns nil if it does not have a name and is not saved.
211
+ def columns
212
+ if name
213
+ connection.columns(quote_relation_name('schema' => schema, 'rel_name' => name)).map{|c| {'rel_name' => name, 'col_name' => c.name}.with_indifferent_access}
214
+ end
215
+ end
216
+
217
+ def save
218
+ if @metadata.valid?
219
+ begin
220
+ Metadata.transaction do
221
+ @metadata.save
222
+ connection.execute("CREATE VIEW #{view_name.to_s.force_encoding('UTF-8')} AS #{query.to_s.force_encoding('UTF-8')}")
223
+ return true
224
+ end
225
+ rescue ActiveRecord::StatementInvalid => e
226
+ raise e
227
+ end
228
+ else
229
+ return false
230
+ end
231
+ end
232
+
233
+ def update_sql(params)
234
+ @metadata.params = params
235
+ @single_queries = [Tutuf::VisualQuery::Sql.new('sql' => params['sql'])]
236
+ if @metadata.valid?
237
+ begin
238
+ Metadata.transaction do
239
+ @metadata.save
240
+ connection.execute("DROP VIEW #{view_name}")
241
+ connection.execute("CREATE VIEW #{view_name} AS #{query}")
242
+ return true
243
+ end
244
+ rescue ActiveRecord::StatementInvalid => e
245
+ @metadata.errors.add(:base, e.to_s)
246
+ return false
247
+ end
248
+ else
249
+ return false
250
+ end
251
+ end
252
+
253
+ def view_name
254
+ "#{quote_ident(self.class.schema)}.#{quote_ident(name)}"
255
+ end
256
+
257
+ def destroy
258
+ begin
259
+ Metadata.transaction do
260
+ @metadata.destroy
261
+ connection.execute("DROP VIEW #{quote_ident(self.class.schema)}.#{quote_ident(name)}")
262
+ end
263
+ return true
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end