简体   繁体   中英

Rails API - How to build a query to filter an array of strings?

I need to make a simple API and add the following functionality:

GET /tools?tag=node (EXAMPLE)

The Tool model has the following columns:
* title (string)
* description (text)
* link (string)
* tags (array of strings)

I need to be able to search from localhost all the tools that have some tag in it. I try to use Rack Reducer but this method only works on string or text types.

I know this should be simple, but I'm new in Rails.

Thanks for any help.

schema.rb:

ActiveRecord::Schema.define(version: 2019_11_29_170156) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "tools", force: :cascade do |t|
    t.string "title"
    t.string "link"
    t.text "description"
    t.text "tags", default: [], array: true
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end

tools_controller.rb

class ToolsController < ApplicationController

  ToolReducer = Rack::Reducer.new(
    Tool.all,
    # ->(tags:) { select { |item| item[:tags].match(/#{tags}/i) } }
    ->(tags:) { where("tags @> ?", "%#{tags}%") }
  )

  def index
    # @tools = Tool.where(query_params)
    @tools = ToolReducer.apply(params)
    # @tools = Tool.all
    render json: @tools
  end

  def new
    @tool = Tool.new
  end

  def show
    @tool = Tool.find(params[:id])
    render json: @tool
  end

  def create
    @tool = Tool.new(tool_params)
    if @tool.save
      render json: @tool
      redirect_to @tool
    end
  end

  def destroy
    @tool = Tool.find(params[:id])
    @tool.destroy
  end

  private

    def tool_params
      params.require(:tool).permit(:title, :link, :description, :tags)
    end
end

pg_search gem is pretty good to make search in a Postgres database.

Yet, in your case, I think your app has a bad design.

Instead of storing tags in a field of the "tool" model, you should maybe create a new model called "tags" and create a has_and_belongs_to_many relationship between the tags model and the tool model.

It will be much easier to find and delete tags.

(Also anything rack related goes deeper into your app, it is middleware. I don't know Rack::reducer but I am sure you don't need it)

This is a really trivial task to accomplish without anything other than Rails if you actually setup your database properly first.

You want to create a normalized tags table and a join table that links tools and tags:

class Tool < ApplicationRecord
  has_many :taggings
  has_many :tags, through: :taggings
end

# rails g model tag name:string:uniq
class Tag < ApplicationRecord
  validates_uniqueness_of :name
  has_many :taggings
  has_many :tools, through: :taggings
end

# rails g model tagging tool:belongs_to tag:belongs_to
class Tagging < ApplicationRecord
  belongs_to :tool
  belongs_to :tag
end

This lets you filter tools by joining the tags model:

# has at least one of the tags
Tool.joins(:tags)
    .where(tags: { name: ["foo", "bar", "baz"] })

# has all the tags
Tool.joins(:tags)
    .where(tags: { name: ["foo", "bar", "baz"] })
    .group('tools.id')
    .having('count(*) = 3')

You can pass arrays in Rack by ending the parameter names with [] :

Started GET "/tools?tags[]=foo&tags[]=bar" for ::1 at 2019-12-02 23:49:37 +0100
   (0.4ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
Processing by ToolsController#index as HTML
  Parameters: {"tags"=>["foo", "bar"]}

This how its done by the Rails form helpers when you create selects and checkboxes. You could also use something like a comma seperated list and manually split the parameter.

def index
  @tools = Tool.all
  if params[:tags].any?
    @tools = @tools.joins(:tags).where(tags: { name: params[:tags] })
  end
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM