Skip to content

Commit 192c6c9

Browse files
committed
Doc: Understanding SearchableMixin class
1 parent abf32f7 commit 192c6c9

File tree

1 file changed

+82
-0
lines changed

1 file changed

+82
-0
lines changed

search/implement_elasticseach.md

+82
Original file line numberDiff line numberDiff line change
@@ -380,3 +380,85 @@ class SearchableMixin(object):
380380

381381
The latest version of Flask-sqlalchemy will return the error `sqlalchemy.exc.ArgumentError: The "whens" argument to case(), when referring to a sequence of items, is now passed as a series of positional elements, rather than as a list.` if a list is used.
382382

383+
To trigger changes from the SQLAlchemy database such as before and after a commit is made, we can define class methods for each.
384+
385+
```python
386+
# app/models.py: Before and after commit
387+
388+
class SearchableMixin(object):
389+
# ...
390+
391+
@classmethod
392+
def before_commit(cls, session):
393+
session._changes = {
394+
'add': list(session.new),
395+
'update': list(session.dirty),
396+
'delete': list(session.deleted)
397+
}
398+
399+
@classmethod
400+
def after_commit(cls, session):
401+
for obj in session._changes['add']:
402+
if isinstance(obj, SearchableMixin):
403+
add_to_index(obj.__tablename__, obj)
404+
for obj in session._changes['update']:
405+
if isinstance(obj, SearchableMixin):
406+
add_to_index(obj.__tablename__, obj)
407+
for obj in session._changes['delete']:
408+
if isinstance(obj, SearchableMixin):
409+
remove_from_index(obj.__tablename__, obj)
410+
session._changes = None
411+
```
412+
413+
Just before a session is committed, the `before_commit()` handler will allow us to check what object has been added, modified or deleted through `session.new`, `session.dirty` and `session.delete` respectively. These objects are not going to be available anymore after a commit is made. `session._changes` dictionary allows us to save the objects and have them survive a commit since we shall be using them to update the Elasticsearch index.
414+
415+
As soon as a session has been successfully committed, this is the proper time to make changes on the Elasticsearch side of things using `after_commit()`. We begin by iterating over what has been added, modified or deleted and make corresponding calls to the indexing functions in the `search` module for objects with `SearchableMixin`.
416+
417+
We can include a simple `reindex()` helper method that can allow us to refresh an index with all the data in the relational side. You may experience instances where you have to manually update the Elasticsearch index to ensure the latest changes are effected.
418+
419+
```python
420+
class SearchabelMixin(object):
421+
# ...
422+
423+
@classmethod
424+
def reindex(cls):
425+
for obj in cls.query:
426+
add_to_index(cls.__tablename__, obj)
427+
```
428+
429+
Given that `reindex` is a class method, you can run `Model.reindex()` to update the Elasticsearh index. Finally, to ensure that SQLAlchemy listens to database changes events, we can call the function `db.event.listen()` from SQLAlchemy. This function serves to call `before_commit` and `after_commit` methods before and after each commit respectively.
430+
431+
```python
432+
# app/models.py: Listen to database changes
433+
434+
class SearchableMixin(object):
435+
# ...
436+
437+
db.event.listen(db.session, 'before_commit', SearchableMixin.before_commit)
438+
db.event.listen(db.session, 'after_commit', SearchableMixin.after_commit)
439+
```
440+
441+
To ensure that `SearchableMixin` is fully incorporated into a model, we can pass it as a class argument to the select model as follows:
442+
443+
```python
444+
# app/models.py: Integrate SearchableMixin into a model
445+
446+
class Post(SearchableMixin, db.Model):
447+
# ...
448+
```
449+
450+
With this minor change to the `Post` model, we can maintain a full text-search for posts. Let us begin by initializing all the index from the posts currently in the database:
451+
452+
```python
453+
>>> Post.reindex()
454+
```
455+
456+
And to search, we can do:
457+
458+
```python
459+
>>> query, total = Post.search('test the Japan', 1, 20)
460+
>>> total
461+
7
462+
>>> query.all()
463+
[Post: test comment, Post: Another one test for you, Post: Why the diss, Post: Japan the great country, Post: I am the greatest, Post: Two of the tests were not done well, Post: There has got to be the best way around it]
464+
```

0 commit comments

Comments
 (0)