visual_query 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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