Skip to content
This repository has been archived by the owner on Apr 16, 2023. It is now read-only.

Incompatible structure on seemingly compatible relations #1

Closed
craysiii opened this issue Sep 18, 2015 · 14 comments
Closed

Incompatible structure on seemingly compatible relations #1

craysiii opened this issue Sep 18, 2015 · 14 comments

Comments

@craysiii
Copy link

I was pretty eager to utilize this gem, but have run into a snag.

# User.rb
class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :confirmable

  has_many :wikis
# has_many :wikis, through: :collaborators
  has_many :collaborators
  has_many :collaborated_wikis, through: :collaborators, source: :wiki

  after_initialize :init

  def init
    self.role ||= 'standard'
  end

  def admin?
    role == 'admin'
  end

  def premium?
    role == 'premium'
  end

  def standard?
    role == 'standard'
  end

  def publicize_wikis
    self.wikis.each { |wiki|
      wiki.update_attributes(private: false)
      wiki.collaborators.delete_all
    }
  end

end
# Wiki.rb
class Wiki < ActiveRecord::Base
  belongs_to :user

  has_many :collaborators
  has_many :users, through: :collaborators

  has_paper_trail :only => [:title, :body]
  has_permalink

  after_initialize :init

  def init
    self.private = false if self.private.nil?
  end

  def candidates
    User.where.not(id: self.users).where.not(id: self.user).where.not(role: 'admin')
  end

  scope :public_wikis, -> { where(private: false) }
end
# Collaborator.rb
class Collaborator < ActiveRecord::Base
  belongs_to :user
  belongs_to :wiki
end
# wiki_policy.rb
class WikiPolicy < ApplicationPolicy

  def index?
    true
  end

  def show?
    super && ( (!record.private) || (record.user == user || user.in?(record.users)) || (user.present? && user.admin?) )
  end

  def edit?
    show?
  end

  def destroy?
    user.present? && (record.user == user || user.admin?)
  end

  def private?
    user.present? && ((user.premium? && (record.user == user || record.new_record?)) || user.admin?)
  end

  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user  = user
      @scope = scope
    end

    def resolve
      wikis = []
      if user.present? && user.admin?
        wikis = scope.all
      elsif user.present? && user.premium?
        # Here lies the issue - Incompatible relations
        wikis = scope.public_wikis.or(user.wikis).or(user.collaborated_wikis)
      else
        wikis = scope.public_wikis
      end
      wikis
    end
  end

end

So User has_many wikis that they own, and has_many collaborated wikis through collaborator. Wiki's has many users through collaborator. When I try:

wikis = scope.public_wikis.or(user.wikis).or(user.collaborated_wikis)

I get an error from user.collaborated_wikis, which should return the relation of wikis related to this user. user.wikis works the same, by providing the wikis owned by user through collaborators. If I take out the collaborated_wikis call, even though scope.public_wikis and user.wikis are of different classes (ActiveRecord_Relation vs ActiveRecord_Associations_CollectionProxy, respectively) they play well together. If you look at the class of user.collaborated_wikis, it is that of ActiveRecord_Associations_CollectionProxy. Going into the rails console, I can see that the result of user.collaborated_wikis is a list of all the wikis that the user collaborates on, so I know the HMT relation works, I just can't figure out why the gem is saying that the structures are incompatible. Any ideas?

@Eric-Guo
Copy link
Owner

Can you try the master version? gem 'where-or', github: 'Eric-Guo/where-or'

I just blindly port from Rails master in structurally_compatible_for_or check. (no testing.)

unless structurally_compatible_for_or?(other)
  raise ArgumentError, 'Relation passed to #or must be structurally compatible'
end

@craysiii
Copy link
Author

I installed the master version per your instructions. Now I am getting a different error.

uninitialized constant ActiveRecord::QueryMethods::Relation:

Extracted source (around line #100):
    def new_where_clause
      Relation::WhereClause.empty # Line 100
    end
    alias new_having_clause new_where_clause
  end

This happens on any page, even one that doesn't have a call to 'where-or'.

@Eric-Guo
Copy link
Owner

Sorry, it's not relative(in where-or gem, we should always say ActiveRecord::Relation::WhereClause.empty), maybe I port too much, just rollback the change, now OK?

@craysiii
Copy link
Author

https://github.com/Eric-Guo/where-or/blob/master/lib/where-or.rb#L88 Also needs the explicit ActiveRecord::Relation

@Eric-Guo
Copy link
Owner

Yes, I just testing, kindly trying.. again.. I'm also testing, there is no testing suite in this gem 😄

@craysiii
Copy link
Author

I am happy to test with you, I realize that this is relatively new!

Now I am getting the first error: "Relation passed to #or must be structurally compatible."

I can provide more context for each relation if you need it.

Question: https://github.com/Eric-Guo/where-or/blob/master/lib/where-or.rb#L90 Is the comparison on this line supposed to be != ? Maybe it should be == ? I am not sure, just a guess.

@Eric-Guo
Copy link
Owner

I'm working one this now, please wait, seems much harder problem than just copy from master..

@craysiii
Copy link
Author

I will be patient.

@craysiii
Copy link
Author

More contextual information:

[2] pry(main)> u.wikis
  Wiki Load (0.2ms)  SELECT "wikis".* FROM "wikis" WHERE "wikis"."user_id" = ?  [["user_id", 1]]
=> [#<Wiki:0x00000007999ba0
  id: 1,
  title: "Nobis et molestiae rerum qui.",
  body:
   "[\"Eligendi pariatur soluta aliquid id accusamus. Officia a quam illo velit quisquam non. Repudiandae ea consectetur voluptates ut expedita reiciendis. Accusamus ab autem. Saepe in dolores magnam rerum quam voluptatibus praesentium.\", \"Cum eligendi rerum earum optio natus dolores. Voluptatem accusantium aut quasi nemo molestiae. Ea esse consectetur eos. Sit dolorem nam aut possimus. Et suscipit temporibus.\", \"Odit quam fugit quasi sunt voluptatem nisi. Nihil ipsum veritatis omnis. Deleniti pariatur et quam non ipsum. Modi inventore magnam amet assumenda quo et. Voluptas voluptatem sed iure dolor doloribus qui.\"]",
  private: false,
  user_id: 1,
  created_at: Thu, 10 Sep 2015 20:19:56 UTC +00:00,
  updated_at: Thu, 10 Sep 2015 20:19:56 UTC +00:00,
  permalink: "nobis-et-molestiae-rerum-qui">, ...]
[3] pry(main)> u.collaborated_wikis
  Wiki Load (0.2ms)  SELECT "wikis".* FROM "wikis" INNER JOIN "collaborators" ON "wikis"."id" = "collaborators"."wiki_id" WHERE "collaborators"."user_id" = ?  [["user_id", 1]]
=> [#<Wiki:0x00000007c989c8
  id: 65,
  title: "some wiki title",
  body: "some body here",
  private: false,
  user_id: 9,
  created_at: Fri, 18 Sep 2015 20:37:55 UTC +00:00,
  updated_at: Fri, 18 Sep 2015 20:37:55 UTC +00:00,
  permalink: "some-wiki-title">]
[4] pry(main)> Wiki.public_wikis
  Wiki Load (1.1ms)  SELECT "wikis".* FROM "wikis" WHERE "wikis"."private" = ?  [["private", "f"]]
=> [#<Wiki:0x00000002f85d98
  id: 1,
  title: "Nobis et molestiae rerum qui.",
  body:
   "[\"Eligendi pariatur soluta aliquid id accusamus. Officia a quam illo velit quisquam non. Repudiandae ea consectetur voluptates ut expedita reiciendis. Accusamus ab autem. Saepe in dolores magnam rerum quam voluptatibus praesentium.\", \"Cum eligendi rerum earum optio natus dolores. Voluptatem accusantium aut quasi nemo molestiae. Ea esse consectetur eos. Sit dolorem nam aut possimus. Et suscipit temporibus.\", \"Odit quam fugit quasi sunt voluptatem nisi. Nihil ipsum veritatis omnis. Deleniti pariatur et quam non ipsum. Modi inventore magnam amet assumenda quo et. Voluptas voluptatem sed iure dolor doloribus qui.\"]",
  private: false,
  user_id: 1,
  created_at: Thu, 10 Sep 2015 20:19:56 UTC +00:00,
  updated_at: Thu, 10 Sep 2015 20:19:56 UTC +00:00,
  permalink: "nobis-et-molestiae-rerum-qui">, ...]


[5] pry(main)> u.wikis.class
=> Wiki::ActiveRecord_Associations_CollectionProxy
[6] pry(main)> u.collaborated_wikis.class
=> Wiki::ActiveRecord_Associations_CollectionProxy
[7] pry(main)> Wiki.public_wikis.class
=> Wiki::ActiveRecord_Relation

@Eric-Guo
Copy link
Owner

I plan to writing a testing case in Rails master (Rails 5) to reproduce such case, to see if it's same or work.

I just remove structurally_compatible_for_or check, Kindly try that branch if work for your case.

gem 'where-or', github: 'Eric-Guo/where-or', branch: 'no_structurally_compatible_check'

if it work, means just need loose the checking condition at structurally_compatible_for_or, otherwise means Rails 5 also need to handle that case usage, using has_many :collaborated_wikis, through: :collaborators, source: :wiki in or

@craysiii
Copy link
Author

It does not work, seemingly because that relation (User.collaborated_wikis) has a JOIN statement.

SQLite3::SQLException: no such column: collaborators.user_id: SELECT "wikis".* FROM "wikis" WHERE (("wikis"."private" = ? OR "wikis"."user_id" = ?) OR "collaborators"."user_id" = ?)

@craysiii
Copy link
Author

This query gave me what I wanted:

wikis = Wiki.find_by_sql(
          ['SELECT "wikis".* FROM "wikis" WHERE ("wikis"."private" = ? OR "wikis"."user_id" = ?) UNION ALL SELECT "wikis".* FROM "wikis" INNER JOIN "collaborators" ON "wikis"."id" = "collaborators"."wiki_id" WHERE "collaborators"."user_id" = ?;', false, user.id, user.id])

So by using UNION ALL between the SQL generated by" wiki.public_wikis.or(user.wikis)" and "user.collaborated_wikis", I was able to get the result. I don't know if #or should be responsible for this complex logic. I don't necessarily think you should work on this anymore, if it's in line with Rails 5. Maybe this should be it's own method (ActiveRecord#union) so one could write the query like so:

wikis = Wiki.public_wikis.or(user.wikis).union(user.collaborated_wikis)

Thanks for looking at this and let me know if you'd like further testing.

@bf4
Copy link
Collaborator

bf4 commented Sep 21, 2015

So, what I put in the gist is what we're using in production, fwiw

@craysiii
Copy link
Author

Could probably close this non-issue 😄

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants