heartcombo/has_scope

Apply scope on a different model than controller

hidr0 opened this issue ยท 6 comments

hidr0 commented

I am in a category_controller.
And a category has many category_content_refs
I have a category_content_ref scope in the category_content_ref model which is

scope :tag, -> {...}

I would like to do this thing
When I get a request on /category/some-category?tag=tag-name

 apply_scopes(@category.category_content_refs)

I want to apply the scope tag on the category_content_refs, is that possible?

@Hidroo did you ever figure this out?

hidr0 commented

@elijahmurray nope. Not even close.

tegon commented

Hi everyone, just to make sure we're on the same page: do you guys want to call a scope on a different entity from the controller - e.g. call the scope on category_content_refs inside a category_controller - or do you have multiple scopes in the same controller and you want to call some on category and others on category_content_refs?

@tegon I can't speak for everyone but for me personally, I'd love to do something like calling my Book scopes in my Categories controller in the event I am using book objects in the category controller. If that makes sense. Apologies if this can already be done and I overlooked this feature.

The way to get this behaviour is to organise your model scopes properly and "delegate" some scopes to the correct model.

Lets say we have two models: Book and Author

class Author
  # schema: id, :name
  has_many :books
end
class Book
  # id, :title, :author_id
  belongs_to :author
end

And a Books controller with an index action where you want to search by different fields

class BooksController
  def index
    #...
  end
end

You're required to search books by:

  1. Book title
  2. Author name

You first have to think whose responsibility it is to search an author by name and a book by title. It's each class' responsibility to implement these scopes.

class Author
  # schema: id, :name
  has_many :books

  scope :name_is, -> (arg) { where(name: arg) }
end
class Book
  # id, :title, :author_id
  belongs_to :author
  
  scope :title_is, -> (arg) { where(title: arg) }
end

Now, if you want to search books by the author's name, you can do the following:

class Book
  # id, :title, :author_id
  belongs_to :author
  
  scope :title_is, -> (arg) { where(title: arg) }
  scope :author_name_is, -> (arg) { joins(:author).merge(Author.name_is(arg)) }
end

author_name_is becomes a delegator of the message.

Now your controller can respond to author_name_is which is a scope of book but delegates it's responsibility to the correct class.

class BooksController
  has_scope :title_is
  has_scope :author_name_is

  def index
    apply_scopes(Book)
  end
end

Thanks for the detailed explanation @pinzonjulian ๐Ÿ‘ ๐Ÿ‘Œ

I'll go ahead and close the issue, but please let me know if you are having any problem with setting it up, we could add some documentation around that to help.