Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested associations support #67

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion app/views/tabulatr/_tabulatr_paginator.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -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
31 changes: 18 additions & 13 deletions lib/tabulatr/data/data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'
Expand Down
14 changes: 10 additions & 4 deletions lib/tabulatr/data/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion lib/tabulatr/data/filtering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down
3 changes: 2 additions & 1 deletion lib/tabulatr/renderer/association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions spec/dummy/app/models/vendor.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
class Vendor < ActiveRecord::Base
has_many :products
belongs_to :parent
end
1 change: 1 addition & 0 deletions spec/dummy/app/tabulatr_data/product_tabulatr_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion spec/dummy/db/migrate/20130730132101_create_vendors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def change
t.boolean :active
t.text :description

t.timestamps
t.timestamps null: false
end
end
end
2 changes: 1 addition & 1 deletion spec/dummy/db/migrate/20130730132321_create_products.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def change
t.decimal :price
t.boolean :active

t.timestamps
t.timestamps null: false
end
end
end
2 changes: 1 addition & 1 deletion spec/dummy/db/migrate/20130730132348_create_tags.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
35 changes: 23 additions & 12 deletions spec/dummy/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 4 additions & 2 deletions spec/features/tabulatrs_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions spec/lib/tabulatr/data/data_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
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

it 'uses default order' do
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
Expand All @@ -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
Expand Down
21 changes: 10 additions & 11 deletions spec/lib/tabulatr/data/filtering_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -56,23 +55,23 @@ 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

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

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,
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion spec/lib/tabulatr/json_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion spec/lib/tabulatr/params_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
Loading