1
votes

I'm trying to solve the following problem. I have three models: User has_many Camera has_many Recordings.

class AddTables < ActiveRecord::Migration
  def change
    create_table :users
    create_table :cameras do |t|
      t.references :user, null: false, foreign_key: true
      t.timestamp :archived_at, index: true
    end
    create_table :recordings do |t|
      t.references :camera, null: false, foreign_key: true
      t.timestamp :archived_at, index: true
    end
  end
end

Camera and Recording have the special field "archived_at" to mark a record as deleted (soft delete). I want User#recordings to look through all the cameras (any Camera.archived_at), but, at the same time, I want it to look through only not arcvhied Recordings (Recording.archived_at == nil). I've tried several cases, but have got no success.

=== CASE 1 ===

class User < ActiveRecord::Base
  has_many :cameras, -> { unscope where: :archived_at }
  has_many :recordings, through: :cameras
end

class Camera < ActiveRecord::Base
  default_scope { where(archived_at: nil) }

  belongs_to :user
  has_many :recordings
end

class Recording < ActiveRecord::Base
  default_scope { where(archived_at: nil) }

  belongs_to :camera
end

irb(main):013:0> reload!; User.first.recordings(true) Reloading... User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 Recording Load (0.2ms) SELECT "recordings".* FROM "recordings" INNER JOIN "cameras" ON "recordings"."camera_id" = "cameras"."id" WHERE "cameras"."user_id" = $1 [["user_id", 1]] => #]>

=== CASE 2 ===

class User < ActiveRecord::Base
  has_many :cameras, -> { unscope where: :archived_at }
  has_many :recordings, through: :cameras
end

class Camera < ActiveRecord::Base
  default_scope { where(cameras: {archived_at: nil}) }

  belongs_to :user
  has_many :recordings
end

class Recording < ActiveRecord::Base
  default_scope { where(recordings: {archived_at: nil}) }

  belongs_to :camera
end

irb(main):013:0> reload!; User.first.recordings(true) Reloading... User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 Recording Load (0.2ms) SELECT "recordings".* FROM "recordings" INNER JOIN "cameras" ON "recordings"."camera_id" = "cameras"."id" WHERE "cameras"."user_id" = $1 [["user_id", 1]] => #]> irb(main):014:0> reload!; User.first.recordings(true) Reloading... User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 Recording Load (0.2ms) SELECT "recordings".* FROM "recordings" INNER JOIN "cameras" ON "recordings"."camera_id" = "cameras"."id" WHERE "recordings"."archived_at" IS NULL AND "cameras"."archived_at" IS NULL AND "cameras"."user_id" = $1 [["user_id", 1]] => #]>

=== CASE 3 ===

class User < ActiveRecord::Base
  has_many :cameras, -> { unscope where: {cameras: :archived_at} }
  has_many :recordings, through: :cameras
end

class Camera < ActiveRecord::Base
  default_scope { where(cameras: {archived_at: nil}) }

  belongs_to :user
  has_many :recordings
end

class Recording < ActiveRecord::Base
  default_scope { where(recordings: {archived_at: nil}) }

  belongs_to :camera
end

irb(main):016:0> reload!; User.first.recordings(true) Reloading... User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 Recording Load (0.2ms) SELECT "recordings".* FROM "recordings" INNER JOIN "cameras" ON "recordings"."camera_id" = "cameras"."id" WHERE "recordings"."archived_at" IS NULL AND "cameras"."archived_at" IS NULL AND "cameras"."user_id" = $1 [["user_id", 1]] => #]>

1

1 Answers

1
votes

Try this out:

user.rb:

class User < ApplicationRecord
    has_many :cameras
    has_many :recordings, -> { unscope(where: :archived_at).where(recordings: {archived_at: nil}) }, through: :cameras
end

camera.rb:

class Camera < ApplicationRecord
    default_scope { where(archived_at: nil) }
    belongs_to :user
    has_many :recordings
end

recording.rb:

class Recording < ApplicationRecord
    belongs_to :camera
    default_scope { where(archived_at: nil) }
end

Here's the output query:

irb(main):088:0> User.first.recordings
User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
Recording Load (0.1ms)  SELECT "recordings".* FROM "recordings" INNER JOIN "cameras" ON "recordings"."camera_id" = "cameras"."id" WHERE "cameras"."user_id" = ? AND "recordings"."archived_at" IS NULL  [["user_id", 1]]