简体   繁体   中英

Rails/mongoid: Advanced querying of arrays

Im stuck with an advanced query in rails. I need a solution that works in mongoid and if possible also active record (probably not possible). I've put together a simplified example below.

Consider the following model:

class Announcement
  include Mongoid::Document

  field :title, type: String
  field :user_group, type: Array
  field :year, type: Array
  field :tags, type: Array

  has_and_belongs_to_many :subjects

  before_save :generate_tags

  private
  def generate_tags
    tags = []
    if self.subjects.present?
      self.subjects.each { |x| tags << x.name.downcase.gsub(" ", "_") }
    end

    if self.year.present?
      self.year.each { |x| tags << "year_" + x.to_s }
    end

    self.tags = tags
  end
end

Given the tags array of document 1:

["hsc_mathematics", "hsc_chemistry", "year_9"]

And document 2:

["hsc_mathematics", "hsc_chemistry"]

And document 3:

["hsc_mathematics", "hsc_chemistry", "year_9", "year_10"]

And document 4:

["year_9", "year_10"]

Now consider the following model:

class Student < User
  include Mongoid::Document

  field :year, type: Integer
  has_many :subjects

  def announcements
      tags = []
      if self.subjects.present?
        self.subjects.each { |x| subjects << x.name.downcase.gsub(" ", "_") }
      end

      tags << "year_" + self.year.to_s

      Announcement.where("user_group" => { "$in" => ["Student", "all_groups"]}).any_of({"tags" => { "$in" => tags }}, {tags: []})
  end
end

For the purpose of our example our student has the following tags:

[ "hsc_mathematics", "hsc_physics", "year_10" ]  

My query is incorrect as I want to return documents 2, 3 and 4 but not document 1.

I need the query to adhere to the following when returning announcements:

i. If the announcement has subject tags match on any subject

ii. If the announcement has year tags match on any year

iii. If announcement has year and subject tags match on any year and any subject

How would I go about writing this?

EDIT

Im happy to split year out of my tags but im still stuck

Announcement.where("user_group" => { "$in" => ["Student", "all_groups"]}).any_of({"tags" => { "$in" => ["hsc_mathematics", "hsc_physics"] }}, {tags: []}).any_of({"year_at_school" => { "$in" => 10 }}, {year_at_school: []})

So the solution was to adjust my models and use a more organised query rather then an entire tag bank.

Announcement model:

class Announcement
  include Mongoid::Document

  field :title, type: String
  field :user_group, type: Array, default: [""]
  field :year, type: Array, default: [""]
  field :tags, type: Array, default: [""]

  has_and_belongs_to_many :subjects

  before_save :generate_tags

  private
  def generate_tags
    tags = []
    if self.subjects.present?
      self.subjects.each { |x| tags << x.name.downcase.gsub(" ", "_") }
    end

    self.tags = tags
  end
end

User model:

class Student < User
  include Mongoid::Document

  field :year, type: Integer
  has_many :subjects

  def announcements
     year = "year_" + self.year.to_s
      tags = [""]
      if self.subjects.present?
        self.subjects.each { |x| tags << x.name.downcase.gsub(" ", "_") }
      end

      Announcement.where("user_group" => { "$in" => ["Student", ""] }).and("year" => { "$in" => [year, ""]}).any_in(tags: tags).all.entries
  end
end

EDIT: Heres a neater version of the query as suggested

This example also has an expiry field which assumes nil = never expires

Announcement.where(:user_group.in => ["Student", ""], :year.in => [year, ""], :tags.in => tags).any_of({:expires_at.gte => Time.zone.now}, {:expires_at => nil}).all.entries

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