bogdan / datagrid

Gem to create tables grids with sortable columns and filters
MIT License
1.02k stars 115 forks source link

Generic array, dynamic filters #230

Closed jsimpson closed 6 years ago

jsimpson commented 7 years ago

Hello! First of all, thank you for such a useful gem! It's been a great help in more than one Rails application that I've written over the years.

Currently, I am bumping up against a problem that I believe I have discovered is currently unsupported. I've looked in to the code myself and I believe that I understand a couple of things about how it works at a lower level against this use-case.

What I'm doing is I'm trying to hook up dynamic filters to a generic array scope. I can hack the Datagrid::Drivers::Array#normalized_column_type method to return a :string and it actually works great (only for string columns, obviously), but I recognize that this is clearly not the correct way to do this. However, it does lead me to believe that with some effort I could potentially add this feature in a clean way for some set of columns types (like strings) and that it may be useful to others.

The problem I'm hitting is that I'm unable to get the scope inside of the dynamic filter (grid_class.scope is simply an empty array), I believe because I am using instance-level scoping when I initialize my grid object.

I am wondering if you could provide some guidance on how to find the instance-level scope inside of the driver in this case. Am I way off base here? Is this even possible? I feel like I am probably missing or overlooking some obvious detail.

My Grid class looks something like this...

class SomeGrid
  include Datagrid

  scope { [] }

  filter(:condition, :dynamic, operations: ["=~"], select: [["ColumnA", :column_a], ["ColumnB", :column_b]], header: "Search")

  column(:column_a)
  column(:column_b)
end

My controller looks something like this...

class SomeController < ApplicationController
  def index
    somethings = ApiWrapper.something.all
    @grid = SomeGrid.new(params[:something_grid]) { |scope| somethings }
  end
end

Everything else is rather standard per the documentation and my previous experience.

Thanks again for this wonderful gem and in advance for any assistance you can offer!

jsimpson commented 7 years ago

Thinking through it a little bit more, I can get the scope if I move my API call inside of the scope block in my grid class. While not really ideal, it does provide a solution. From there, it was fairly straight-forward to determine the arrays field type and return the expected column type. I'm going to keep thinking about this in the background, but I would really appreciate any insight you can offer in to this particular problem and this approach to it. Thanks!

bogdan commented 7 years ago

There is a way to fix the issue so that column types are defined from instance scope but not class scope. That would be tough change and will require a lot of efforts.

I don't want to add complexity to the architecture just because of your use case.

That is why I am proposing the following workaround:

somethings = ApiWrapper.something.all
grid_class = Class.new(SomeGrid)) do
  scope { somethings }
end
@grid = grid_class.new(params[:something_grid])
brendon commented 6 years ago

I also have this use case. In my case also there is always a base level scope rather than allowing the user to query the entire database table (e.g. records relating to one particular parent record in the site). I'd assume this was fairly common? Right now I've successfully overridden the scope in the controller as recommended in the wiki but have come unstuck if I want to provide a select menu with options only relevant to the overall dynamic grid scope.

You're right though. It'd be quite an architectural change.

brendon commented 6 years ago

I found a better way:

class SomeGrid
  attr_accessor :something
  include Datagrid

  filter(:item, :enum, select: :available_items) do |value|
    whatever...
  end

  def available_items
    something.items.map(&:name)
  end
end

in the controller:

@grid = SomeGrid.new(some_grid_params)
@grid.something = something

Your method based enumerator will now have access to the object you passed in (e.g. your scope).