From 08cb3eb3cb17b34ac4b08cf84e3e38fd74f45ba5 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Fri, 18 May 2018 23:57:15 -0500 Subject: [PATCH] Chained associations This closes #66 --- lib/tabulatr/data/data.rb | 24 +++++++++---- lib/tabulatr/data/dsl.rb | 16 ++++++--- lib/tabulatr/renderer/association.rb | 3 +- spec/dummy/app/models/vendor.rb | 1 + .../tabulatr_data/product_tabulatr_data.rb | 1 + spec/dummy/db/schema.rb | 35 ++++++++++++------- spec/features/tabulatrs_spec.rb | 5 +-- 7 files changed, 58 insertions(+), 27 deletions(-) diff --git a/lib/tabulatr/data/data.rb b/lib/tabulatr/data/data.rb index 3794e21..5396eb1 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') @@ -123,14 +123,24 @@ 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 diff --git a/lib/tabulatr/data/dsl.rb b/lib/tabulatr/data/dsl.rb index 2aca83a..20490bf 100644 --- a/lib/tabulatr/data/dsl.rb +++ b/lib/tabulatr/data/dsl.rb @@ -47,18 +47,24 @@ def column(name, opts = {}, &block) def association(assoc, name, opts = {}, &block) @table_columns ||= [] - assoc_klass = main_class.reflect_on_association(assoc.to_sym) - tname = assoc_klass.try(:quoted_table_name) || assoc_klass.try(:klass).try(:quoted_table_name) + 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) } + tname = assoc_klass.try(:quoted_table_name) sql_options = determine_sql(opts, tname, 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/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/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 328d54a..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')