ActiveRecord query with alias'd table names

后端 未结 2 1041
予麋鹿
予麋鹿 2020-12-09 06:11

Using model concerns which include scopes, what is the best way to write these knowing that nested and/or self-referencing queries are likely?

In one of my concerns,

相关标签:
2条回答
  • 2020-12-09 07:09

    Well, well, well. After quite a big time looking through the sources of Arel, ActiveRecord and Rails issues (it seems this is not new), I was able to find the way to access the current arel_table object, with its table_aliases if they are being used, inside the current scope at the moment of its execution.

    That made possible to know if the scope is going to be used within a JOIN that has the table name aliased, or if on the other hand the scope can be used on the real table name.

    I just added this method to your Expirable concern:

    def self.current_table_name
      current_table = current_scope.arel.source.left
    
      case current_table
      when Arel::Table
        current_table.name
      when Arel::Nodes::TableAlias
        current_table.right
      else
        fail
      end
    end
    

    As you can see, I'm using current_scope as the base object to look for the arel table, instead of the prior attempts of using self.class.arel_table or even relation.arel_table, which as you said remained the same regardless of where the scope was used. I'm just calling source on that object to obtain an Arel::SelectManager that in turn will give you the current table on the #left. At this moment there are two options: that you have there an Arel::Table (no alias, table name is on #name) or that you have an Arel::Nodes::TableAlias with the alias on its #right.

    With that table_name you can revert to your first attempt of #{current_table_name}.#{lower_bound_field} and #{current_table_name}.#{upper_bound_field} in your scopes:

    def self.lower_bound_column
      "#{current_table_name}.#{lower_bound_field}"
    end
    
    def self.upper_bound_column
      "#{current_table_name}.#{upper_bound_field}"
    end
    
    scope :current_and_future, ->(as_at = Time.now) { where("#{upper_bound_column} IS NULL OR #{upper_bound_column} >= ?", as_at) }
    scope :current_and_expired, ->(as_at = Time.now) { where("#{lower_bound_column} IS NULL OR #{lower_bound_column} <= ?", as_at) }
    

    This current_table_name method seems to me to be something that would be useful to have on the AR / Arel public API, so it can be maintained across version upgrades. What do you think?

    If you are interested, here are some references I used down the road:

    • A similar question on SO, answered with a ton of code, that you could use instead of your beautiful and concise Ability.
    • This Rails issue and this other one.
    • And the commit on your test app on github that made tests green!
    0 讨论(0)
  • 2020-12-09 07:09

    I have a slightly modified approach from @dgilperez, which uses the full power of Arel

    def self.current_table_name
     current_table = current_scope.arel.source.left
    end
    

    now you could modify your methods with arel_table syntax

    def self.lower_bound_column
     current_table[:lower_bound_field]
    end
    
    def self.upper_bound_column
      current_table[:upper_bound_field]
    end
    

    and use it query like this

     lower_bound_column.eq(nil).or(lower_bound_column.lteq(as_at))
    
    0 讨论(0)
提交回复
热议问题