ruby 链轨.如果有任何选择真的

ecfsfe2w  于 8个月前  发布在  Ruby
关注(0)|答案(3)|浏览(80)

我在Rails应用程序中编写的一个条件查询有一个问题。目前,如果某些位置(location_north,location_south等)为真,以及某些类型(type_dining,type_unique等)为真,我会查询Stores,但我希望它查询它们是否为两个,而不是所有的都为真。
例如,如果Lead的location_north和location_east设置为true,我想查询所有具有location_north或location_east的Store,Store不需要都设置为true。
类型相同,如果Lead具有store_type_unique true和store_type_dining true,我想查询具有type_unique OR type_dining true的所有商店,商店不需要都设置为true。
我知道我的数据库的组织方式存在问题,但是否可以在当前版本中设置此数据库?感谢您的任何指导,非常感谢!

def build_filters_obj
        filters = []
        filters.push 'location_north' if @lead.location_north
        filters.push 'location_east' if @lead.location_east
        filters.push 'location_south' if @lead.location_south
        filters.push 'location_west' if @lead.location_west
        filters.push 'location_other' if @lead.location_other
        
        return filters
    end

    def perform(lead_id)
        @lead = Lead.find(lead_id)

        lead_email = ValidEmail2::Address.new(@lead.email)
        UserNotifierMailer.send_signup_email(@lead).deliver if lead_email.valid?

        @stores = Store.all
        @stores = @stores.where.not(email: [nil, ''])

        n = @lead.guests_total.to_i
        @stores = @stores.where("capacity_min <= ? AND capacity_max >= ?", n, n)
        
        @stores = @stores.where(:type_unique => true) if @lead.store_type_unique
        @stores = @stores.where(:type_dining => true) if @lead.store_type_dining
        @stores = @stores.where(:type_hotel => true) if @lead.store_type_hotel
        
        filters = build_filters_obj
        filters.each do |filter|
            @stores = @stores.where(filter.to_sym => true)
        end

        @stores = @stores.or(Store.where(:receive_all => true))

        @stores.each do |store|
            store_email = ValidEmail2::Address.new(store.email)
            UserNotifierMailer.send_lead_email(store, @lead).deliver if store_email.valid?
        end
    end

字符串

ki1q1bka

ki1q1bka1#

我认为这里的挑战是基于@lead的属性动态构建OR查询。下面是perform方法的新方法:

def perform(lead_id)
    @lead = Lead.find(lead_id)

    lead_email = ValidEmail2::Address.new(@lead.email)
    UserNotifierMailer.send_signup_email(@lead).deliver if lead_email.valid?

    n = @lead.guests_total.to_i
    @stores = Store.where.not(email: [nil, ''])
                  .where("capacity_min <= ? AND capacity_max >= ?", n, n)

    # Dynamic OR conditions for types
    type_conditions = []
    type_conditions << Store.where(type_unique: true) if @lead.store_type_unique
    type_conditions << Store.where(type_dining: true) if @lead.store_type_dining
    type_conditions << Store.where(type_hotel: true) if @lead.store_type_hotel

    # Dynamic OR conditions for locations
    location_conditions = build_filters_obj.map { |filter| Store.where(filter.to_sym => true) }

    # Combine type and location conditions
    combined_conditions = type_conditions + location_conditions

    # Apply OR conditions if there are any
    unless combined_conditions.empty?
        combined_query = combined_conditions.shift
        combined_conditions.each do |condition|
            combined_query = combined_query.or(condition)
        end
        @stores = @stores.merge(combined_query)
    end

    # Include stores that receive all
    @stores = @stores.or(Store.where(receive_all: true))

    # Send emails to stores
    @stores.each do |store|
        store_email = ValidEmail2::Address.new(store.email)
        UserNotifierMailer.send_lead_email(store, @lead).deliver if store_email.valid?
    end
end

字符串

p8ekf7hl

p8ekf7hl2#

一个非常快速的改变是重写每一行:

@stores = @stores.where(:type_unique => true) if @lead.store_type_unique

字符串
对此:

@stores = @stores.or(Store.where(:type_unique => true)) if @lead.store_type_unique


对于filters循环中的行也是如此。
然后,编写测试以确保得到正确的逻辑。最后,您可以重构以DRY up代码。

u4vypkhs

u4vypkhs3#

两个想法:
1.至少有一种方法可以让你的应用程序以当前的方式构建/组织。
1.我不会使用它们中的任何一个。这个应用程序需要重构,现在是一个很好的时间去做。
由于Rails or方法需要一个以object类为参数的查询,因此您当前的选项有点棘手。看起来您想要有电子邮件地址的商店。这样的查询意味着or正在抓取没有电子邮件地址的商店。

@stores = Store.where.not(email: [nil, '']).where(:type_unique => true)
@stores = @stores.or(Store.where(:type_dining => true))

字符串
您可以在Store类上定义一个作用域,然后将其应用于两者,即。

class Store
  scope :with_email, () -> { where.not(email: [nil, '']) }
end

def perform
  ...
  @stores = Store.with_email.where(:type_unique => true)
  @stores = @stores.or(Store.with_email.where(:type_dining => true))
end


但是,假设你重构了你的应用程序,这样你就有了一个名为store_type的列,而不是每种商店类型的列。我在这里使用enum,但为了简单起见,让我们假设它是一个字符串字段。然后上面的代码就变成了这样:

@stores = Store.where.not(email: [nil, '']).where(store_type: ["unique", "dining"])


这相当于一个store_type IN ("unique", "dining") SQL语句。这就是您要查找的内容。
此外,如果Lead的位置存储为数组字符串字段,如果您的应用程序使用PostgreSQL数据库,则@lead.location将返回类似["east", "north"]的数组,并且您可以为此废弃build_filters_obj方法:

@stores = @stores.where(location: @lead.location)


重构会让你的perform方法更容易被其他开发人员(包括你自己)阅读和理解。如果你真的走这条路,你需要重构一些数据库数据。有很好的工具可以做到这一点。

  • AfterParty gem已经有一段时间没有更新了,但它是解决这类问题的一个很好的gem。
  • Shopify有一个Maintenance Tasks gem也可以用于此目的。

这两个应用程序本质上都是在后台运行rake任务,你需要这样做来转换你的数据。
1.部署具有并使用新数据库列(即location、store_type)的新代码。
1.使用上述gem之一来转换旧数据以使用新格式和列。
1.部署删除现在失效的数据库列(即location_north、location_west等)的代码。
我通常在一个版本中执行步骤1和步骤2,在下一个版本中执行步骤3,但这都是关于适合您的应用程序的内容以及它的停机能力。

相关问题