简体   繁体   English

如何在 Hanami 中重构查询链?

[英]How to refactor queries chain in Hanami?

How to refactor #filtered method?如何重构#filtered方法?

In Hanami there is no way to make a chain of queries (filters) in ActiveRecord-style.在 Hanami 中,无法以 ActiveRecord 样式进行查询链(过滤器)。 I would like to get a methods like ActiveRecord filters.我想获得像 ActiveRecord 过滤器这样的方法。

Now: documents.filtered(genre: 'news', min_published_at: from, max_published_at: to, skip: 30)现在: documents.filtered(genre: 'news', min_published_at: from, max_published_at: to, skip: 30)

What I want: documents.with_genre('news').published_between(from, to).skip(30)我想要的是: documents.with_genre('news').published_between(from, to).skip(30)

class DocumentRepository < Hanami::Repository
  GENRES = DbSchema.current_schema.enum(:document_genre).values.map(&:to_s)
  DOCUMENTS_PER_PAGE = 30

  associations do
    has_many :boxes
    has_many :urls
  end

  # rubocop:disable Metrics/CyclomaticComplexity
  # rubocop:disable Metrics/AbcSize
  def filtered(params = {})
    result = ordered.limit(DOCUMENTS_PER_PAGE)

    result = result.where(genre: params[:genre]) if params.key?(:genre)

    if params.key?(:min_created_at) && params.key?(:max_created_at)
      date_range = params[:min_created_at]..params[:max_created_at]
      result = result.where(created_at: date_range)
    end

    if params.key?(:min_published_at) && params.key?(:max_published_at)
      date_range = params[:min_published_at]..params[:max_published_at]
      result = result.where(published_at: date_range)
    end

    result = result.offset(params[:skip]) if params.key?(:skip)

    result
  end
  # rubocop:enable Metrics/CyclomaticComplexity
  # rubocop:enable Metrics/AbcSize

  def ordered
    documents.order { created_at.desc }
  end
end

Something along these lines might work, but not sure how chaining these will poorly effect performance or results, but you can try it and it may lead you to the answer you want沿着这些路线的一些东西可能会起作用,但不确定链接这些会如何影响性能或结果,但您可以尝试它,它可能会引导您找到您想要的答案

UPDATED更新

If you really want chaining this is close to what you want.如果你真的想要链接,这接近你想要的。

class DocumentRepository < Hanami::Repository
  GENRES = DbSchema.current_schema.enum(:document_genre).values.map(&:to_s)
  DOCUMENTS_PER_PAGE = 30

  associations do
    has_many :boxes
    has_many :urls
  end

  attr_accessor :data

  def initialize
    @data = []
    super  
  end

  def data 
    @data.flatten!.uniq!
  end

  def with_genre(key)
    @data << documents.where(genre: key) 
    self
  end

  def published_between(arr)
    from, to = arr 
    @data << documents.where(created_at: [from..to])
    self
  end

  def skip(num)
    @data << documents.offset(num)
    self
  end

end

Call it like this assuming this is an instance variable of DocumentRepository假设这是DocumentRepository的实例变量,这样调用它

document_repository.with_genre('news')
                   .published_between([from, to])
                   .skip(30)
                   .data

By returning self in each instance method you're able to chain the calls on the instance.通过在每个实例方法中返回self ,您可以链接实例上的调用。

Original answer原答案

This way works but uses similar syntax in your current call.这种方式有效,但在您当前的调用中使用类似的语法。

class DocumentRepository < Hanami::Repository
  GENRES = DbSchema.current_schema.enum(:document_genre).values.map(&:to_s)
  DOCUMENTS_PER_PAGE = 30

  associations do
    has_many :boxes
    has_many :urls
  end

  def hack_where(opts={})
    data = []
    opts.each do |i|
      data << self.send(i[0],i[1]).call
    end
    data.flatten!.uniq!
  end

  def with_genre(key)
    lambda { |key| documents.where(genre: key) }
  end

  def published_between(arr)
    from = arr[0]
    to = arr[1]
    lambda { |from, to| documents.where(created_at: [from..to]) }
  end

  def skip(num)
    lambda { documents.offset(num) }
  end

end

You can call it like:你可以这样称呼它:

hack_where({with_genre: 'news', published_between: [from,to], skip: 30})

Introduce query object:引入查询对象:

class FilterDocuments
  DOCUMENTS_PER_PAGE = 30

  def initialize(documents)
    @documents = documents
  end

  def filter(params = {})
    result = apply_ordering(documents)
    result = apply_limit_and_offset(result, params)
    result = filter_by_genre(result, params)
    result = filter_by_created_at(result, params)
    result = filter_by_published_at(result, params)

    result
  end

private
  attr_reader :documents

  def apply_ordering(documents)
    documents.order { created_at.desc }
  end

  def apply_limit_and_offset(documents, params)
    if params.key?(:skip)
      documents.offset(params[:skip])
    else
      documents
    end.limit(DOCUMENTS_PER_PAGE)
  end

  def filter_by_genre(documents, params)
    if params.key?(:genre)
      documents.where(genre: params[:genre])
    else
      documents
    end
  end

  def filter_by_created_at(documents, params)
    if params.key?(:min_created_at) && params.key?(:max_created_at)
      range = params[:min_created_at]..params[:max_created_at]
      documents.where(created_at: range)
    else
      documents
    end
  end

  def filter_by_published_at(documents, params)
    if params.key?(:min_published_at) && params.key?(:max_published_at)
      range = params[:min_published_at]..params[:max_published_at]
      documents.where(published_at: range)
    else
      documents
    end
  end
end

How to use:如何使用:

    def query
      FilterDocuments.new(DocumentRepository.new.documents)
    end

    filtered_documents = query.filter(params)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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