-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feedback requested: Pass through options to scope block #75
base: master
Are you sure you want to change the base?
Conversation
@@ -44,26 +44,26 @@ def search_scope(name, &block) | |||
search_scopes[name] = SearchScope.new(name, self) | |||
search_scopes[name].instance_exec(&block) | |||
|
|||
send(:define_singleton_method, name) { |query, query_options = {}| search_cop(query, name, query_options) } | |||
send(:define_singleton_method, "unsafe_#{name}") { |query, query_options = {}| unsafe_search_cop(query, name, query_options) } | |||
send(:define_singleton_method, name) { |query, query_options = {}, scope_options = {}| search_cop(query, name, query_options, scope_options) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The syntax would be cleaner with keyword arguments. Is that possible to do without a breaking change?
return respond_to?(:scoped) ? scoped : all if query.blank? | ||
|
||
query_builder = QueryBuilder.new(self, query, search_scopes[scope_name], query_options) | ||
|
||
scope = instance_exec(&search_scopes[scope_name].reflection.scope) if search_scopes[scope_name].reflection.scope | ||
scope = instance_exec(scope_options, &search_scopes[scope_name].reflection.scope) if search_scopes[scope_name].reflection.scope |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the whole point here. I also considered doing **scope_options
, which might be nicer?
Hi, thanks for opening this! However, i think i don't fully understand what you try to achieve. If you want to scope your search queries to a particular user, doing it outside of search cop is definitely the way to go. You have a thousand options to do so. Otherwise please describe again why you think this is "error prone" and also please describe how you want the search cop DSL to look like with your desired approach. Thanks in advance! |
Honest question: is it? Why? Here's my thoughts:
I'm both sick and tired, so please let me know if any of this isn't making sense. In terms of API, I don't really care. All I'm looking for is a central place for all of this coupled code, that works, and that allows me to fix bugs and delivery features that require associations that require user information. Some options would be |
Thanks for the exhaustive answer. Regarding "why outside of search cop": similar to unix philsophy, do one thing and do it good, all the scoping activerecord is already perfectly good at. Regarding "the meaning of scope": A search scope in search cop terminology is the set of fields the search is scoped to. Besides, before i try to cover each point individually please tell me what would be wrong with: class MyModel
# ...
search_scope :internal_search do
# ...
end
def self.search(q, user:)
where(user: user).internal_search(q)
end
end Again, unix philosophy, do one thing good and then mix together the good things. Moreover, everything is at the same place. You can add all neccessary joins, etc. It even matches the same interface if done right. |
I'm trying to understand what you're saying vs what the README says. Specifically, from the README, "If you [...] need to perform special operations, specify a scope." "You can as well use scope together with aliases to perform arbitrarily complex joins and search in the joined models/tables." This is what I'm trying to do, it's just that my complex join requires access to the user ID (pretty arbitrary, right? hah :) ). In this thread, I believe you're actually making an argument that Am I understanding you correctly that you'd actually like to remove the Thanks for the feedback and discussion! |
Hey -- following up on this :). Would love to get off our custom fork, but do need a way to pass in the user to the search scope. I spent days debugging that and it's the only way to get a performant search for our product. Is there anything I could do to move it along? |
hey, @bmulholland thanks for the heads up. Maybe you can share some actual code which shows how you use this. I have a bit of a hard time following. Thanks in advance. |
Sure! Here's a very simplified version of the configuration, including the detailed comments I made about it: search_scope :search do
attributes name: ["records.title", "user_records.title"]
scope do |user|
self
# user_records are the user's manually set name for
# the record. Note that this will search ALL user's names
# for this record unless additional scope is put to limit
# the search to where user_records.user is the current user
# or nil (for when they haven't renamed the item)
#
# WARNING: MUST use user_id in the join condition -- otherwise the left
# join won't work as intended. We want blank rows for on the right of
# the joins if there are no matches for this user, and without the
# user_id constraint then that won't happen because there are results
# for *other* users that fill in the gaps. Then those users would get
# filtered out by the where condition, resulting in missed results.
#
# NOTE: By manually writing the joins and including user_id and
# tenant_id, we hint to Postgres that it can efficiently do the join
# without checking tenant_id. This shaves off 10s for some joins!
# Some detail about this approach here: https://towardsdatascience.com/how-we-optimized-postgresql-queries-100x-ff52555eabe
.joins("LEFT OUTER JOIN user_records ON "\
"user_records.record_id = records.id AND "\
"user_records.tenant_id = records.tenant_id AND "\
"user_records.user_id = #{user.id}")
# These are filtered in the `where` -- though...
# TOOD: Try filtering the table_for_permissions join too ON user_id
.joins(:table_for_permission)
.where("table_for_permission.user": user)
end
end Usage is: |
Thanks! this made things much clearer. Generally, i'd then be ok with passing those options to the scope as shown in your proposal. I also think we could change it to keyword arguments without introducing a breaking change, as send(:define_singleton_method, name) { |query, default_operator: nil, scope_options: nil| ... } |
Soliciting feedback on this feature, hence draft. Our search is scoped per-user, and so the search scope must include conditions that the data belongs to this user.
Basic scenario is to add these to a
where
that's part of the scope. An error-prone workaround to that is to apply thewhere
after the search, outside of search_copMore advanced scenario is building custom subqueries to optimize complex joins for searching. For those, the where clause cannot be built outside of the scope because it is necessarily part of the scope (think
.joins(Model.join(...).where().to_sql
). In those scenarios, this feature is required to use thescope
at all -- and possibly to usesearch_cop
at all.Thoughts on this approach? Other ideas? Is this feature welcomed? What else (tests, I assume) would need to be done to get this merged in?