1

I'm a newbie trying to implement the datagrid gem (https://github.com/bogdan/datagrid). Unfortunately, I'm getting three error messages:

Error 1: undefined method 'filter' for #<UsersGrid:0x000000044e9400>
Referring to: line `@grid.filter` in def index

Error 2: undefined local variable or method 'user' for #<UsersController:0x007f821f434808>
Referring to: line `link_to view_context.image_tag("delete.gif",...` in the controller.

Error 3: undefined method `image_tag' for UsersGrid:Class
Referring to: line `link_to image_tag(user.avatar.url,` in users_grid.rb
Placing `view_context` in front of link_to in users_grid.rb doesn't work either: undefined local variable or method 'view_context' for UsersGrid:Class.
Also tried `ActionController::Base.helpers.image_tag`. Although that seems to solve the image_tag issue, I then get the error message: `undefined method 'user_path' for #<ActionView::Base:0x007f821d3115b8>` referring to that same line.

Removing all the lines above, and the form works :-)

Any suggestions how to change the code below to adjust for these errors?


My code: After installing the gem I have created /app/grids/users_grid.rb:

class UsersGrid
  include Datagrid

  scope do
    User.order("users.created_at desc")
  end

  filter(:id, :integer)
  filter(:email, :string) { |value| where('email like ?', "%#{value}%") }
  filter(:organization, :string) { |value| where('organization like ?', "%#{value}%") }
  filter(:verified, :xboolean)
  filter(:created_at, :date, :range => true, :default => proc { [1.month.ago.to_date, Date.today]})

  column(:id)
  column(:avatar) do |user|
    if user.avatar?
      link_to image_tag(user.avatar.url, alt: "Profile"), user_path(user)       #Error3
    else
      link_to image_tag("profile.gif", alt: "Profile"), user_path(user)
    end
  end
  column(:organization)
  column(:verified) do |user|
    image_tag("verifiedaccount.gif", title: "verified") if user.verified
  end
  column(:created_at) do |model|
    model.created_at.to_date
  end
end

The users controller:

def index
  @grid = UsersGrid.new(params[:users_grid]) do |scope|
    scope.where(admin: false).page(params[:page]).per_page(30)
  end
  @grid.assets
  if (current_user && current_user.admin?)        # This is a Sessions helper method.
    @grid.filter(:activated, :xboolean, :default => true)                    #Error1
    @grid.column(:myadmin, :header => "My admin?") do |user|
      view_context.image_tag("adminamina.png", title: "admin") if user.myadmin
    end
    @grid.column(:activated) do |user|
      user.activated_at.strftime('%v') if user.activated
    end
    @grid.column(:remove) do |user|
      link_to view_context.image_tag("delete.gif", title: "remove"), user, method: :delete,
                                    data: { confirm: "Please confirm" }       #Error2
    end
  end
end

The view:

<%= datagrid_form_for @grid, :method => :get, :url => users_path %>
<%= will_paginate(@grid.assets) %>
<%= datagrid_table(@grid) %>
<%= will_paginate(@grid.assets) %>
Nick
  • 3,496
  • 7
  • 42
  • 96

1 Answers1

4

This DataGrid seems to be set up more or less like a Model class, and I think that's an important hint that they're serious about object orientation and encapsulation: the innards of the class won't automatically have access to the outside world, you need to manually specify the info you need.

One technique called dependency injection might be useful here, and fortunately DataGrid appears to have decent support for it. Quoted from this wiki page:

To pass an object to the Grid instance, simply merge it to the params hash on initialization:

grid = TheGrid.new(params[:the_grid].merge(current_user: current_user))

You can then access it in the Grid object by declaring the corresponding getter

def TheGrid
   ...
   attr_accessor :current_user
   ...
end

So, when you new up the UserGrid object, specify current_user: current_user as part of the hash, then the grid should have access to it.

Then you'll have one more problem: the UserGrid object will have access to current_user, but the UserGrid class won't, and you've written your conditional in at the class level. Again the wiki has an answer for this, it's called dynamic columns and you can click that link to see a couple examples.

A brief dynamic columns example: in the controller (or wherever you create your UserGrid), try something like the following (again, more examples at the above link):

user_grid = UserGrid.new(same_params_as_before)

# The Grid object has been created, but we can still add columns to it after-the-fact
if current_user && current_user.admin?
  # This block will only execute if the above condition is met. We can
  # define as many extra columns as we feel like here (or change the 
  # user_grid object in any other way we want)
  user_grid.column(:myadmin, header: "My admin?") do |user|
    image_tag("adminamina.png", title: "admin") if user.myadmin
  end

  ...
end

Some notes about this example:

  • The UserGrid object doesn't even need to know about current_user. It doesn't care that you're an admin; the controller simply decides whether you're an admin, and if so, makes some changes to the UserGrid object.
  • Consequently, you can get rid of the current_user method definition inside the UserGrid class, plus all code that references it.
  • This is an example of bad object-oriented programming because the controller is tasked with managing the internal state of another object (ie. manually defining more columns on the UsersGrid in certain conditions). I like this approach because it's direct and easier to understand, but be aware that there are better ways to organize this code so that all of the UserGrid "column definition stuff" is kept in the same place, not scattered around different files.
Community
  • 1
  • 1
Topher Hunt
  • 4,404
  • 2
  • 27
  • 51
  • Thanks, this is really advanced stuff for me. I have added `def current_user` to users_grid.rb and .merge to the params in the users controller. Added it to the original post. I'm not really sure how to adjust the column part of users_grid.rb (i.e., how to implement the dynamic columns, as you suggest). Because of this the error message is still the same. – Nick May 25 '15 at 19:32
  • No problem, I added an example above. – Topher Hunt May 25 '15 at 19:38
  • 1
    Appreciate it! I updated the original post. Removed `def current_user` and the new params. Moved the filter and columns that depend on current_user to the controller. I got a new error message (see original post). – Nick May 25 '15 at 20:36
  • Ah that image_tag thing is because controllers aren't normally expected to use view helpers. See [this other StackOverflow answer](http://stackoverflow.com/a/11161692/1729692), in particular you'll probably need to say `view_context.image_tag(...)` instead of just `image_tag()`. – Topher Hunt May 26 '15 at 12:12
  • Thanks Topher. This helped. Updated the original post with I'm afraid three new errors. – Nick May 26 '15 at 13:38
  • Looks like I got a downvote. Care to explain what I could do differently next time? – Topher Hunt Apr 29 '19 at 09:35