Project

ii_finder

0.0
The project is in a healthy, maintained state
A base finder to support building relations from parameters
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

 Project Readme

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.