IIFinder

A base finder to support building relations from parameters.

Dependencies

  • ruby 2.3+
  • activesupport 5.0+

Installation

Add this line to your application's Gemfile:

gem 'ii_finder'

Then execute:

$ bundle

Usage

Prepare model:

class Item < ActiveRecord::Base
end

Prepare finder:

class ItemsFinder < IIFinder::Base
  parameters :name

  def name(value)
    @relation.where(name: value)
  end
end

Use finder as follows:

ItemsFinder.call(name: 'NAME').to_sql
#=> SELECT "items".* FROM "items" WHERE "items"."name" = 'NAME'

You can also specify relation as first argument:

ItemsFinder.call(Item.where(id: [1, 2, 3]), name: 'NAME').to_sql
#=> SELECT "items".* FROM "items" WHERE "items"."id" IN (1, 2, 3) AND "items"."name" = 'NAME'

Finder

Finder loops keys of parameters and call the corresponding method with value of parameter as argument. Finder method will not be called when the value of parameter is blank. If you want to receive such value, set allow_blank as follows:

class ItemsFinder < IIFinder::Base
  parameters :name, allow_blank: true

  def name(value)
    @relation.where(name: value)
  end
end

ItemsFinder.call(name: '').to_sql
#=> SELECT "items".* FROM "items" WHERE "items"."name" = ''

Finder has following attributes:

class ItemsFinder < IIFinder::Base
  parameters :name

  def name(value)
    puts "relation: #{@relation}"
    puts "criteria: #{@criteria}"
    puts "model: #{@model}"
    puts "table: #{@table}"
  end
end

ItemsFinder.call(name: 'NAME')
#=> relation: #<Item::ActiveRecord_Relation:
#   criteria: {:name=>'NAME'}
#   model: Item
#   table: #<Arel::Table ...>

Return value of each finder method is merged into @relation if it is a kind of ActiveRecord::Relation. In case you want to merge by yourself, set configuration as follows:

IIFinder.configure do |config|
  config.merge_relation = false
end

class ItemsFinder < IIFinder::Base
  parameters :name

  def name(value)
    @relation = @relation.where(name: value)
  end
end

ItemsFinder.call(name: 'NAME').to_sql
#=> SELECT "items".* FROM "items" WHERE "items"."name" = 'NAME'

Callbacks

Following callbacks are available.

  • before_call
  • around_call
  • after_call

For example:

class ItemsFinder < IIFinder::Base
  after_call :default_order

  def default_order
    @relation = @relation.order(id: :desc)
  end
end

ItemsFinder.call.to_sql
#=> SELECT "items".* FROM "items" ORDER BY "items"."id" DESC

Note that finder does not handle the return value of callback. When you want to update @relation in the callback, reassign @relation or use methods like where! or order!.

Coactors

You can chain multiple finders by using coact. For example:

class NameFinder < IIFinder::Base
  parameters :name

  def name(value)
    @relation.where(name: value)
  end
end

class AgeFinder < IIFinder::Base
  parameters :age

  def age(value)
    @relation.where(age: value)
  end
end

class ItemsFinder < IIFinder::Base
  coact NameFinder, AgeFinder
end

ItemsFinder.call(Item.all, name: 'name', age: 10).to_sql
#=> SELECT "items".* FROM "items" WHERE "items"."name" = 'name' AND "items"."age" = 10

See coactive for more coact examples:

Lookup for model

Finder lookups related model by its class name when the first argument of call is not relation. So the name of finder class should be composed of the name of model class. For example:

class Item < ActiveRecord::Base
end

class ItemsFinder < IIFinder::Base
end

IIFinder::Base.lookup(ItemsFinder)
#=> Item

Note that superclass of finder is also looked up until related model is found.

class Item < ActiveRecord::Base
end

class ItemsFinder < IIFinder::Base
end

class InheritedItemsFinder < ItemsFinder
end

IIFinder::Base.lookup(InheritedItemsFinder)
#=> Item

Scope for model

In case you want to call finder from model, include IIFinder::Scope into model as follows:

class Item < ActiveRecord::Base
  include IIFinder::Scope
end

Item.finder_scope(name: 'NAME').to_sql
#=> SELECT "items".* FROM "items" WHERE "items"."name" = 'NAME'

Logging

Finder supports instrumentation hook supplied by ActiveSupport::Notifications. You can enable log subscriber as follows:

IIFinder::LogSubscriber.attach_to :ii_finder

This subscriber will write logs in debug mode as the following example:

Calling ItemsFinder with {:id=>1}
...
Called ItemsFinder (Duration: 9.9ms, Allocations: 915)

Contributing

Bug reports and pull requests are welcome at https://github.com/kanety/ii_finder.

License

The gem is available as open source under the terms of the MIT License.