1

I am trying to run following query through Rails query interface but unable to translate my logic. The query is

Select f.* from feeds f
Left join feed_items fi on fi.id = f.feedable_id
where 
  f.feedable_type in ('Homework', 'Datesheet')
  and
  (
    (fi.assignable_type = 'Level' and assignable_id IN (1)) or
    (fi.assignable_type = 'Student' and assignable_id IN (1)) or
    (fi.assignable_type = 'Section' and assignable_id IN (1)) 
  )

Scenario:

I receive following params hash in my action containing filters which will be added dynamically in my query

{"page"=>"1", "limit"=>"2", "type_filter"=>["Homework", "Datesheet"], "assignable_filter"=>{"Student"=>"[2]", "Section"=>"[1]", "Level"=>"[1]"}}

So far, what I have done is joining the tables and added where clause for type filter but not sure how to dynamically add assignable_filters. Here is my rails code, options are params in following code

def get_feeds(options)
    base = Feed.includes(:feed_item)
    base = add_type_filters base, options
    base = add_assignable_filters base, options
    format_response base, options
end

def add_type_filters(base, options)
    type_filter = options[:type_filter]
    if !type_filter.nil? and type_filter.length > 0
      base = base.where('feedable_type IN (?)', options[:type_filter])
    end
    base
end

def add_assignable_filters(base, options)
    assignable_filter = options[:assignable_filter]
    if !assignable_filter.nil?
      assignable_filter.each do |key, value|
        # code for adding filters combined with or conditions
      end
      # wrap the or conditions and join them with an and in main where clause
    end
    base
end

P.S I am using rails 5

ᄂ ᄀ
  • 5,669
  • 6
  • 43
  • 57
Umair Abid
  • 1,453
  • 2
  • 18
  • 35
  • First thing that I notice: Isn't it feed and feed_items (one to many relationship?). If yes `Feed.includes(:feed_items)` would be correct. – haffla Jul 31 '17 at 11:31
  • And here you can see how to do OR queries in Rails5: https://stackoverflow.com/questions/32753168/rails-5-activerecord-or-query – haffla Jul 31 '17 at 11:43
  • @haffla its a one to one association – Umair Abid Jul 31 '17 at 15:19
  • `base = base.where('feedable_type IN (?)', options[:type_filter])` should be `base.where(feedable_type: options[:type_filter])` this will build an "IN" clause. right now the query is `"feedable_type in (\"[\"Homework\", \"Datesheet\"]\")"` because the `Array` will be converted to a `String` – engineersmnky Jul 31 '17 at 20:13

1 Answers1

0

There was no straight forward way of building the query dynamically. I had to construct the where string to solve the problem. My current solution is

def get_feeds(options)
    params_hash = {}
    type_filters = add_type_filters options, params_hash
    assignable_filters = add_assignable_filters options, params_hash

    where = type_filters
    where = where ? "#{where} and (#{assignable_filters})" : assignable_filters

    base = Feed.eager_load(:feed_item).where(where, params_hash)
    format_response base, options
end

def add_type_filters(options, params_hash)
    type_filter = options[:type_filter]
    type_filter_sql = nil
    if !type_filter.nil? and type_filter.length > 0
      type_filter_sql = 'feeds.feedable_type in (:type_filter)'
      params_hash[:type_filter] = type_filter
    end
    type_filter_sql
end

def add_assignable_filters(options, params_hash)
    assignable_filter_sql = []
    assignable_filter = options[:assignable_filter]
    if !assignable_filter.nil?
      assignable_filter.each do |key, value|
        assignable_filter_sql.push("(feed_items.assignable_type = '#{key}' and feed_items.assignable_id IN (:#{key}))")
        params_hash[key.to_sym] = JSON.parse(value)
      end
    end
    assignable_filter_sql.join(' or ')
end
Umair Abid
  • 1,453
  • 2
  • 18
  • 35