diff --git a/.travis.yml b/.travis.yml index 922916d..4e0ddc4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ rvm: # - 2.1.10 - 2.2.6 - 2.3.3 -# - 2.4.0 # wait for json > 1.8.3 - - jruby-9.1.5.0 + - 2.4.2 + - jruby-9.1.13.0 script: "bundle exec rspec spec" diff --git a/Gemfile b/Gemfile index 7c2f977..9cead1a 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,7 @@ source 'https://rubygems.org' gemspec gem 'jquery-rails' +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw] group :development, :test do gem 'better_errors' diff --git a/app/views/tabulatr/_tabulatr_paginator.html.slim b/app/views/tabulatr/_tabulatr_paginator.html.slim index 3ccc72c..6f430ab 100644 --- a/app/views/tabulatr/_tabulatr_paginator.html.slim +++ b/app/views/tabulatr/_tabulatr_paginator.html.slim @@ -19,6 +19,6 @@ / OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION / WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -- if table_options[:paginate] == true || ((table_options[:paginate].is_a?(Fixnum)) && klass.count > table_options[:paginate]) +- if table_options[:paginate] == true || ((table_options[:paginate].is_a?(Integer)) && klass.count > table_options[:paginate]) .pagination data-table=table_id ul.pagination data-table=table_id diff --git a/lib/tabulatr/data/data.rb b/lib/tabulatr/data/data.rb index 3794e21..9eedffc 100644 --- a/lib/tabulatr/data/data.rb +++ b/lib/tabulatr/data/data.rb @@ -29,7 +29,7 @@ def initialize(relation) @base ||= relation.respond_to?(:klass) ? relation.klass : relation @table_name = @base.table_name @search = self.class.instance_variable_get('@search') || HashWithIndifferentAccess.new - @includes = Set.new() + @includes = Hash.new(Set.new()) @cname = @base.name.downcase @batch_actions = nil @row = self.class.instance_variable_get('@row') @@ -89,7 +89,7 @@ def data_for_table(params, locals: {}, default_order: nil, controller: nil, &blo def execute_batch_actions batch_param, selected_ids if batch_param.present? && @batch_actions.present? batch_param = batch_param.keys.first.to_sym if batch_param.is_a?(Hash) - selected_ids = @relation.map(&:id) if selected_ids.empty? + selected_ids = @relation.pluck(:id) if selected_ids.empty? @batch_actions.yield(Invoker.new(batch_param, selected_ids)) end end @@ -123,22 +123,27 @@ def check_params(params) def join_required_tables(params) if params[:arguments].present? - tt = (params[:arguments].split(",").select{|s| s[':']}.map do |s| - s.split(':').first - end.uniq.map(&:to_sym)) - tt.delete(@table_name.to_sym) - @includes = @includes + tt + tt = params[:arguments].split(",").select{|s| s[':']}.map do |s| + # assoc1-assoc12-assoc123:column3 + a = s.split(':').first.split('-') + a.delete(@table_name.to_s) + a.map(&:to_sym) # [:assoc1, :assoc12, :assoc123] + end.uniq + @includes = tt.reject(&:empty?).inject(@includes) do |h1, arr| + h2 = arr.reverse.inject { |a, n| { n => a } } + if h2.is_a?(Hash) + h1.merge(h2) { |key, oldval, newval| [oldval, newval] } + else + h1.default << h2 + h1 + end + end end # @relation = @relation.includes(@includes.map(&:to_sym)).references(@includes.map(&:to_sym)) - @relation = @relation.eager_load(@includes.map(&:to_sym)) + @relation = @relation.eager_load(@includes, @includes.default.to_a) # @relation = @relation.group("#{@table_name}.#{@base.primary_key}") end - def table_name_for_association(assoc) - @base.reflect_on_association(assoc.to_sym).table_name - # assoc.to_sym - end - end require_relative './dsl' diff --git a/lib/tabulatr/data/dsl.rb b/lib/tabulatr/data/dsl.rb index 8547c26..0249c10 100644 --- a/lib/tabulatr/data/dsl.rb +++ b/lib/tabulatr/data/dsl.rb @@ -47,17 +47,23 @@ def column(name, opts = {}, &block) def association(assoc, name, opts = {}, &block) @table_columns ||= [] - assoc_klass = main_class.reflect_on_association(assoc.to_sym) + unless assoc.is_a?(Array) + assoc = assoc.to_s.split('-').map(&:to_sym) + end + assoc_klass = assoc.reduce(main_class) { |c,a| c.reflect_on_association(a.to_sym).try(:klass) } sql_options = determine_sql(opts, assoc_klass.try(:quoted_table_name), name) opts = { sort_sql: sql_options[:sort_sql], filter_sql: sql_options[:filter_sql]}.merge(opts) table_column = Tabulatr::Renderer::Association.from( name: name, - klass: assoc_klass.try(:klass), + klass: assoc_klass, col_options: Tabulatr::ParamsBuilder.new(opts), - table_name: assoc, - output: block_given? ? block : ->(record){a=record.send(assoc); a.try(:read_attribute, name) || a.try(name)}) + table_name: assoc.join('-').to_sym, + output: block_given? ? block : lambda do |record| + a = assoc.reduce(record) {|cur,nxt| cur.try(:send, nxt)} + a.try(:read_attribute, name) || a.try(name) + end) @table_columns << table_column end diff --git a/lib/tabulatr/data/filtering.rb b/lib/tabulatr/data/filtering.rb index c3f1878..9abc163 100644 --- a/lib/tabulatr/data/filtering.rb +++ b/lib/tabulatr/data/filtering.rb @@ -43,7 +43,7 @@ def apply_search(query) def apply_filters(filter_params) return unless filter_params - filter_params.permit!.to_hash.with_indifferent_access.each do |param| + filter_params.each do |param| name, value = param next unless value.present? diff --git a/lib/tabulatr/renderer/association.rb b/lib/tabulatr/renderer/association.rb index 77a86c1..5b07bdb 100644 --- a/lib/tabulatr/renderer/association.rb +++ b/lib/tabulatr/renderer/association.rb @@ -39,7 +39,8 @@ def association?() true end def principal_value(record, view) return super if output || block - v = record.send(table_name) + assoc = table_name.to_s.split('-').map(&:to_sym) + v = assoc.reduce(record) { |cur,nxt| cur.try(:send, nxt) } if v && v.respond_to?(:to_a) && name != :count v.map(&:"#{name}") else diff --git a/spec/dummy/app/models/vendor.rb b/spec/dummy/app/models/vendor.rb index 1e562ad..c02725b 100644 --- a/spec/dummy/app/models/vendor.rb +++ b/spec/dummy/app/models/vendor.rb @@ -1,3 +1,4 @@ class Vendor < ActiveRecord::Base has_many :products + belongs_to :parent end diff --git a/spec/dummy/app/tabulatr_data/product_tabulatr_data.rb b/spec/dummy/app/tabulatr_data/product_tabulatr_data.rb index 0a4f054..e361109 100644 --- a/spec/dummy/app/tabulatr_data/product_tabulatr_data.rb +++ b/spec/dummy/app/tabulatr_data/product_tabulatr_data.rb @@ -26,6 +26,7 @@ class ProductTabulatrData < Tabulatr::Data column :active, sortable: false column :updated_at, filter: :date do "#{record.updated_at.strftime('%H:%M %d.%m.%Y')}" end association :vendor, :name, filter: :exact + association %i[vendor parent], :name, filter: :exact, sortable: true, header: 'Parent' association :tags, :title do |r| "'#{r.tags.map(&:title).map(&:upcase).join(', ')}'" end diff --git a/spec/dummy/db/migrate/20130730132101_create_vendors.rb b/spec/dummy/db/migrate/20130730132101_create_vendors.rb index 7a315c6..c77b8bb 100644 --- a/spec/dummy/db/migrate/20130730132101_create_vendors.rb +++ b/spec/dummy/db/migrate/20130730132101_create_vendors.rb @@ -6,7 +6,7 @@ def change t.boolean :active t.text :description - t.timestamps + t.timestamps null: false end end end diff --git a/spec/dummy/db/migrate/20130730132321_create_products.rb b/spec/dummy/db/migrate/20130730132321_create_products.rb index 8aa06cf..d9a318a 100644 --- a/spec/dummy/db/migrate/20130730132321_create_products.rb +++ b/spec/dummy/db/migrate/20130730132321_create_products.rb @@ -6,7 +6,7 @@ def change t.decimal :price t.boolean :active - t.timestamps + t.timestamps null: false end end end diff --git a/spec/dummy/db/migrate/20130730132348_create_tags.rb b/spec/dummy/db/migrate/20130730132348_create_tags.rb index b6957a1..ca4bf2c 100644 --- a/spec/dummy/db/migrate/20130730132348_create_tags.rb +++ b/spec/dummy/db/migrate/20130730132348_create_tags.rb @@ -3,7 +3,7 @@ def change create_table :tags do |t| t.string :title - t.timestamps + t.timestamps null: false end create_table :products_tags, id: false do |t| diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index b225c36..4f5e40f 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -11,40 +11,51 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20141223164833) do +ActiveRecord::Schema.define(version: 20180519000000) do - create_table "products", force: true do |t| + create_table "parents", force: :cascade do |t| + t.string "name" + t.string "url" + t.boolean "active" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "products", force: :cascade do |t| t.integer "vendor_id" t.string "title" t.decimal "price" t.boolean "active" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.datetime "publish_at" - t.string "type" t.integer "status", default: 0 end add_index "products", ["vendor_id"], name: "index_products_on_vendor_id" - create_table "products_tags", id: false, force: true do |t| + create_table "products_tags", id: false, force: :cascade do |t| t.integer "tag_id" t.integer "product_id" end - create_table "tags", force: true do |t| + create_table "tags", force: :cascade do |t| t.string "title" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table "vendors", force: true do |t| + create_table "vendors", force: :cascade do |t| t.string "name" t.string "url" t.boolean "active" t.text "description" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "parent_id" end + add_index "vendors", ["parent_id"], name: "index_vendors_on_parent_id" + end diff --git a/spec/features/tabulatrs_spec.rb b/spec/features/tabulatrs_spec.rb index ddff822..26c6b26 100644 --- a/spec/features/tabulatrs_spec.rb +++ b/spec/features/tabulatrs_spec.rb @@ -12,8 +12,9 @@ "officia", "deserunt", "mollit", "anim", "est", "laborum"] before(:each) do - @vendor1 = Vendor.create!(:name => "ven d'or", :active => true) - @vendor2 = Vendor.create!(:name => 'producer', :active => true) + @parent1 = Parent.create!(:name => "ABC", :active => true) + @vendor1 = Vendor.create!(:name => "ven d'or", :active => true, parent: @parent1) + @vendor2 = Vendor.create!(:name => 'producer', :active => true, parent: @parent1) @tag1 = Tag.create!(:title => 'foo') @tag2 = Tag.create!(:title => 'bar') @tag3 = Tag.create!(:title => 'fubar') @@ -108,6 +109,7 @@ Product.create! end visit one_item_per_page_with_pagination_products_path + wait_for_ajax pages = page.all('.pagination li a[data-page]').map{|a| a['data-page'].to_i} expect(pages).to match_array([1,2,3,10,20]) end diff --git a/spec/lib/tabulatr/data/data_spec.rb b/spec/lib/tabulatr/data/data_spec.rb index 50a971f..a029068 100644 --- a/spec/lib/tabulatr/data/data_spec.rb +++ b/spec/lib/tabulatr/data/data_spec.rb @@ -17,7 +17,7 @@ it 'prefilters the result' do td = Tabulatr::Data.new(Product.where(price: 10)) - td.data_for_table(example_params) + td.data_for_table(ActionController::Parameters.new(example_params)) expect(td.instance_variable_get('@relation').to_sql).to match(/.+WHERE \"products\".\"price\" = 10.+/) end @@ -25,7 +25,7 @@ Product.create([{title: 'foo', price: 5}, {title: 'bar', price: 10}, {title: 'fuzz', price: 7}]) td = Tabulatr::Data.new(Product) - records = td.data_for_table(HashWithIndifferentAccess.new(example_params.merge(product_sort: 'title DESC'))) + records = td.data_for_table(ActionController::Parameters.new(example_params.merge(product_sort: 'title DESC'))) expect(records.count).to eql 3 titles = ['fuzz', 'foo', 'bar'] # raise records.inspect @@ -43,7 +43,7 @@ Product.where(id: ids).destroy_all end }) - td.data_for_table(HashWithIndifferentAccess.new(example_params.merge(product_batch: 'delete', 'tabulatr_checked' => {'checked_ids' => ''}))) + td.data_for_table(ActionController::Parameters.new(example_params.merge(product_batch: 'delete', 'tabulatr_checked' => {'checked_ids' => ''}))) expect(Product.where(active: true).count).to be 0 expect(Product.count).to be 1 end diff --git a/spec/lib/tabulatr/data/filtering_spec.rb b/spec/lib/tabulatr/data/filtering_spec.rb index a4e8b31..2f25785 100644 --- a/spec/lib/tabulatr/data/filtering_spec.rb +++ b/spec/lib/tabulatr/data/filtering_spec.rb @@ -6,7 +6,6 @@ class DummyFilteringClass def table_columns; []; end def filters; []; end - def table_name_for_association(assoc); nil; end end describe '.apply_date_condition' do @@ -29,7 +28,7 @@ def table_name_for_association(assoc); nil; end it "filters for 'today'" do fake_obj = double() allow(fake_obj).to receive_message_chain('col_options.filter_sql') { 'publish_at'} - @dummy.apply_date_condition(fake_obj, {simple: 'today'}) + @dummy.apply_date_condition(fake_obj, {'simple'=>'today'}) result = @dummy.instance_variable_get('@relation') expect(result.count).to be 1 expect(result[0].id).to be @today.id @@ -38,7 +37,7 @@ def table_name_for_association(assoc); nil; end it "filters for 'yesterday'" do fake_obj = double() allow(fake_obj).to receive_message_chain('col_options.filter_sql') { 'publish_at'} - @dummy.apply_date_condition(fake_obj, {simple: 'yesterday'}) + @dummy.apply_date_condition(fake_obj, {'simple'=>'yesterday'}) result = @dummy.instance_variable_get('@relation') expect(result.count).to be 1 expect(result[0].id).to be @yesterday.id @@ -47,7 +46,7 @@ def table_name_for_association(assoc); nil; end it "filters for 'this week'" do fake_obj = double() allow(fake_obj).to receive_message_chain('col_options.filter_sql') { 'publish_at'} - @dummy.apply_date_condition(fake_obj, {simple: 'this_week'}) + @dummy.apply_date_condition(fake_obj, {'simple'=>'this_week'}) result = @dummy.instance_variable_get('@relation') expect(result.count).to be 4 expect(result.map(&:id).sort).to eq [@yesterday.id, @today.id, @week_one.id, @week_two.id].sort @@ -56,7 +55,7 @@ def table_name_for_association(assoc); nil; end it "filters for 'last 7 days'" do fake_obj = double() allow(fake_obj).to receive_message_chain('col_options.filter_sql') { 'publish_at' } - @dummy.apply_date_condition(fake_obj, {simple: 'last_7_days'}) + @dummy.apply_date_condition(fake_obj, {'simple'=>'last_7_days'}) result = @dummy.instance_variable_get('@relation') expect(result.map(&:id).sort).to eq ([@last_seven_days.id, @yesterday.id, @today.id, @week_one.id].sort) end @@ -64,7 +63,7 @@ def table_name_for_association(assoc); nil; end it "filters for 'this month'" do fake_obj = double() allow(fake_obj).to receive_message_chain('col_options.filter_sql') { 'publish_at'} - @dummy.apply_date_condition(fake_obj, {simple: 'this_month'}) + @dummy.apply_date_condition(fake_obj, {'simple'=>'this_month'}) result = @dummy.instance_variable_get('@relation') expect(result.map(&:id).sort).to eq ([@today.id, @week_two.id, @this_month.id]) end @@ -72,7 +71,7 @@ def table_name_for_association(assoc); nil; end it "filters for 'last 30 days'" do fake_obj = double() allow(fake_obj).to receive_message_chain('col_options.filter_sql') { 'publish_at'} - @dummy.apply_date_condition(fake_obj, {simple: 'last_30_days'}) + @dummy.apply_date_condition(fake_obj, {'simple'=>'last_30_days'}) result = @dummy.instance_variable_get('@relation') expect(result.map(&:id).sort).to eq ([ @last_thirty_days.id, @yesterday.id, @last_seven_days.id, @today.id, @@ -83,15 +82,15 @@ def table_name_for_association(assoc); nil; end fake_obj = double() allow(fake_obj).to receive_message_chain('col_options.filter_sql') { 'publish_at'} @dummy.apply_date_condition(fake_obj, { - simple: 'from_to', from: '31.12.2013 15:00', - to: '15.01.2014 00:00'}) + 'simple'=>'from_to', 'from'=>'31.12.2013 15:00', + 'to'=>'15.01.2014 00:00'}) result = @dummy.instance_variable_get('@relation') expect(result.map(&:id)).to eq ([@yesterday.id, @today.id, @week_two.id].sort) end it "exits early if condition is 'none'" do relation_before = @dummy.instance_variable_get('@relation') - @dummy.apply_date_condition(nil, {simple: 'none'}) + @dummy.apply_date_condition(nil, {'simple'=>'none'}) relation_after = @dummy.instance_variable_get('@relation') expect(relation_after).to eq relation_before end @@ -133,7 +132,7 @@ def table_name_for_association(assoc); nil; end it 'can not be called without a block variable' do @dummy.instance_variable_set('@search', ->{'hi'}) - expect{@dummy.apply_search('test')}.to raise_error + expect{@dummy.apply_search('test')}.to raise_error(RuntimeError) end it 'accepts an array of searchable columns' do diff --git a/spec/lib/tabulatr/json_builder_spec.rb b/spec/lib/tabulatr/json_builder_spec.rb index 7403b84..01c8e70 100644 --- a/spec/lib/tabulatr/json_builder_spec.rb +++ b/spec/lib/tabulatr/json_builder_spec.rb @@ -12,7 +12,7 @@ it "complains when a non given attribute other than id is requested" do attribute = {action: :bar} data = {title: 'test', price: '7.0 EUR'} - expect{Tabulatr::JsonBuilder.insert_attribute_in_hash(attribute, data)}.to raise_error + expect{Tabulatr::JsonBuilder.insert_attribute_in_hash(attribute, data)}.to raise_error(Tabulatr::RequestDataNotIncludedError) end # it 'accepts arguments without table name' do diff --git a/spec/lib/tabulatr/params_builder_spec.rb b/spec/lib/tabulatr/params_builder_spec.rb index 6e7d2a6..576ed5c 100644 --- a/spec/lib/tabulatr/params_builder_spec.rb +++ b/spec/lib/tabulatr/params_builder_spec.rb @@ -14,6 +14,6 @@ it 'does not accept a param which is in the DEPRECATED_PARAMS array' do stub_const('Tabulatr::ParamsBuilder::DEPRECATED_PARAMS', [:deprecated_param]) - expect{Tabulatr::ParamsBuilder.new(deprecated_param: 'test')}.to raise_error + expect{Tabulatr::ParamsBuilder.new(deprecated_param: 'test')}.to raise_error(NoMethodError) end end \ No newline at end of file diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 3e39f47..541b0d8 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -13,6 +13,7 @@ require 'spec_helper' Capybara.javascript_driver = :poltergeist +Capybara.server = :webrick Capybara.register_driver :poltergeist do |app| Capybara::Poltergeist::Driver.new(app, {js_errors: true}) diff --git a/spec/support/wait_for_ajax.rb b/spec/support/wait_for_ajax.rb index 1760f99..441fefb 100644 --- a/spec/support/wait_for_ajax.rb +++ b/spec/support/wait_for_ajax.rb @@ -1,7 +1,7 @@ # from http://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara module WaitForAjax def wait_for_ajax - Timeout.timeout(Capybara.default_wait_time) do + Timeout.timeout(Capybara.default_max_wait_time) do loop until finished_all_ajax_requests? end end