-
Notifications
You must be signed in to change notification settings - Fork 59
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
Refactor Talk indexing into a Talk::Index record. #540
base: main
Are you sure you want to change the base?
Conversation
end | ||
|
||
after_create_commit :create_in_index | ||
after_update_commit :update_in_index | ||
after_destroy_commit :remove_from_index |
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.
We get this for free now via dependent: :destroy
!
I don't get what's going on in the tests. In development the query looks correct and finds the talk:
But in tests the query looks different and doesn't find the talk:
The Swapping to |
Ok, if I put
But it still doesn't find the book. |
Ah, something seems to be up when we try to insert the record, hm. It's missing the rowid here.
|
Ok, wow, maybe this is more trouble than it's worth. We have to drop back into raw SQL to get Active Record to put in the |
I like how it all feels really ActiveRecord. |
I'm hoping to get back to this, because I'd like to wrap it up. |
This does take some extra work since Active Record can't detect `rowid` is the primary key. But with some awareness around that, it's pretty smooth sailing and we can entirely remove a lot of the custom SQL with standard Active Record.
e702e0c
to
62dd179
Compare
end | ||
|
||
def reindex | ||
update! id: talk.id, title: talk.title, summary: talk.summary, speaker_names: talk.speaker_names |
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.
We're taking advantage of Active Record automatically generating an update or insert statement based on new_record?
.
Internally, update!
calls create_or_update
which then calls new_record? ? _create_record(&block) : _update_record(&block)
.
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 also means we now take advantage of Active Record skipping needless update
queries when there's no change to the attributes.
extend ActiveSupport::Concern | ||
|
||
included do | ||
self.ignored_columns = [table_name.to_sym, :rank] # Rails parses our virtual table with these extra non-attributes. |
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.
A SQLite fts5
table like this is built on a rowid
primary key and several columns to index. Active Record now understands these types of virtual tables but it doesn't recognize the rowid
at all, not even as a column. (It's probably because the column isn't spelled out anywhere in the schema statements so Active Record has no column to parse there).
So this module encapsulates all the work we need to have Active Record recognize the rowid
column as a column and as the primary key.
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.
Oh yeah, and then Active Record also parses the table name and something called rank
as valid columns, so we need to ignore those since they're internal to the fts5
implementation.
|
||
private | ||
|
||
def attributes_for_create(attribute_names) |
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.
We don't need to override attributes_for_update
, because the rowid
is included in the query_constraints_hash
method, that Active Record calls in _update_record
or wherever it was. The details are a little fuzzy to me now because of all the code I had to sift through.
app/models/talk/searchable.rb
Outdated
def select_snippet(column, offset, tag: "mark", omission: "…", limit: 32) | ||
select("snippet(talks_search_index, #{offset}, '<#{tag}>', '</#{tag}>', '#{omission}', #{limit}) AS #{column}_snippet") | ||
def reindex_all | ||
Talk::Index.delete_all |
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.
@adrienpoly reading this again I'm confused as to why we need to delete the existing ones beforehand? Do you remember?
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.
no I don't remember. Maybe I did that initially to just make sure we get something clean knowing that our data is verylimieted so doing a full rebuild is not really costly
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.
@adrienpoly ah got it. Yeah, I think with this change we could let Active Record decide and spare some needless queries in case there's no update needed.
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.
Talk::Index.delete_all |
@adrienpoly @marcoroth think this is ready now. The |
Thanks I will try it on my db and play a bit with it |
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.
@kaspth I tested it locally and it works great. I pushed a small extra test as I was playing with it.
Overall I like how we are getting closer to the ActiveRecord native API. It is clearly code that I wouldn't have been able to write. At first I was a bit concerned about maintenance in the future. but now that it is here it seems fairly easy to extend it to other models so I am less concerned about maintenance.
Looking at ActiveRecord::SQLite::Index code I do see a few hacky things with AR I hope this won't bite us down the road
@marcoroth WDYT ?
Thanks again Kasper for this
Perfect!
Yeah, I've definitely been worried about this too. I think overall, the savings we're getting from using more standard Active Record and the hoops we have to jump through to get that are less concerning than the raw SQL hoops we were doing before. And since that hoop logic is condensed in I'm also planning on doing a write up about how this works for future reference. I'm quite proud of having figured this out really.
For sure! I think |
Looks good to me! |
I'll give this a thorough review tomorrow. Interested to dive in tho |
This does take some extra work since Active Record can't detect
rowid
is the primary key.But with some awareness around that, it's pretty smooth sailing and we can entirely remove a lot of the custom SQL with standard Active Record.