Skip to content
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

Add bitemporal data structure visualizer #94

Merged
merged 1 commit into from
Jul 13, 2022

Conversation

wata727
Copy link
Contributor

@wata727 wata727 commented Jun 23, 2022

Although the Bi-temporal data model is a good data structure, it has a difficult problem to intuitively understand the data structure that has two times, valid time and transaction time.

This PR introduces a visualizer that plots multiple histories on a two-dimensional figure to help you understand the data structure. This visualizer draws the following history as follows:

valid_from valid_to transaction_from transaction_to
2020-07-31 01:43:17.119 9999-12-31 09:00:00.000 2020-07-31 01:43:17.120 2020-07-31 01:43:18.243
2020-07-31 01:43:17.119 2020-07-31 01:43:18.243 2020-07-31 01:43:18.243 9999-12-31 09:00:00.000
2020-07-31 01:43:18.243 9999-12-31 09:00:00.000 2020-07-31 01:43:18.243 2020-07-31 01:43:18.324
2020-07-31 01:43:18.243 2020-07-31 01:43:18.324 2020-07-31 01:43:18.324 9999-12-31 09:00:00.000
2020-07-31 01:43:18.324 9999-12-31 09:00:00.000 2020-07-31 01:43:18.324 9999-12-31 09:00:00.000
[98] pry(main)> puts ActiveRecord::Bitemporal::Visualizer.visualize(employee)

                        | 2020-07-31 01:43:17.119
                        |                 | 2020-07-31 01:43:18.243
                        |                 | | 2020-07-31 01:43:18.324
                        |                 | |                   | 9999-12-31 09:00:00.000
2020-07-31 01:43:17.120 +---------------------------------------+
                        |                                       |
                        |                                       |
                        |                                       |
2020-07-31 01:43:18.243 +-----------------+---------------------+
                        |                 |                     |
2020-07-31 01:43:18.324 |                 +-+-------------------+
                        |                 | |*******************|
                        |                 | |*******************|
                        |                 | |*******************|
9999-12-31 09:00:00.000 +-----------------+-+-------------------+

The area filled with * is the duration of the instance passed to visualize. This makes it easier to understand where the history in the overall history.

Apart from visualize, visualize_records is also provided as a lower-level interface. This method allows you to pass histories and another history to highlight. The following is example:

ActiveRecord::Bitemporal::Visualizer.visualize_records(Employy.ignore_bitemporal_datetime.where("'2019-01-12' < valid_from"), [employee])

In the above example, unlike visualize, you can only draw histories after 2019-01-12, not all history. Alternatively, you can draw the temporal relationships of different associations.

ActiveRecord::Bitemporal::Visualizer.visualize_records(Employee.ignore_bitemporal_datetime, [department])

module_function

def visualize(record, height: 10, width: 40, highlight: true)
histories = record.class.ignore_valid_datetime.within_deleted.bitemporal_for(record)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use ignore_bitemporal_datetime instead of ignore_valid_datetime.within_deleted.

Suggested change
histories = record.class.ignore_valid_datetime.within_deleted.bitemporal_for(record)
histories = record.class.ignore_bitemporal_datetime.bitemporal_for(record)

Comment on lines 24 to 28
valid_times = (histories.map(&:valid_from) + histories.map(&:valid_to)).sort.uniq
transaction_times = (histories.map(&:transaction_from) + histories.map(&:transaction_to)).sort.uniq
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change it to use pluck(:valid_from)?
because #pluck is better performance

Copy link
Contributor Author

@wata727 wata727 Jun 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the histories are already loaded, ActiveRecord::Calculations#pluck is just the same as map(&:valid_from).
https://github.com/rails/rails/blob/v7.0.3/activerecord/lib/active_record/relation/calculations.rb#L194
https://github.com/rails/rails/blob/v7.0.3/activesupport/lib/active_support/core_ext/enumerable.rb#L201

I believe that map(&:valid_from) is better in performance because it is much more efficient in this case to iterate the array than to execute SQL.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey, Thanks :)

@wata727 wata727 force-pushed the bitemporal_visualizer branch 3 times, most recently from d3e64d8 to bc947fa Compare June 27, 2022 02:10
@osyo-manga
Copy link
Collaborator

Thanks :)
It's great!

I have a few questions about a few things that have been bothering me.

  • What do * and # mean?
  • I think it would be easier to see if I add | as follows, is it possible to implement it?
                        | 2020-07-31 01:43:17.119
                        |                 | 2020-07-31 01:43:18.243
                        |                 | | 2020-07-31 01:43:18.324
                        |                 | |                   | 9999-12-31 09:00:00.000
2020-07-31 01:43:17.120 |---------------------------------------|
                        |                                       |
                        |                                       |
                        |                                       |
2020-07-31 01:43:18.243 |-----------------|---------------------|
                        |                 |                     |
2020-07-31 01:43:18.324 |                 |-|-------------------|
                        |                 | |*******************|
                        |                 | |*******************|
                        |                 | |*******************|
9999-12-31 09:00:00.000 |-----------------|-|-------------------|

@wata727
Copy link
Contributor Author

wata727 commented Jul 4, 2022

* indicates where in the entire history the passed history is and emphasizes it. In the above example, it represents a history where valid time is 2020-07-31 01:43:18.324 ~ 9999-12-31 09:00:00.000, transaction time is 2020-07-31 01:43:18.324 ~ 9999-12-31 09:00:00.000 was passed to the argument of visualize. This helps you understand where a history is in the overall history.

# is a zero-length history representation such as valid_from = valid_to or transaction_from = transaction_to. From my experience, zero-length history can cause a lot of problems, so I find it useful to visualize this.

I think it would be easier to see if I add | as follows, is it possible to implement it?

Yes, but the reason I didn't do that was that the header part might look like part of a history. If you don't have this concern, we may implement this.

@osyo-manga
Copy link
Collaborator

Thanks for reply.

Yes, but the reason I didn't do that was that the header part might look like part of a history. If you don't have this concern, we may implement this.

I don't see a problem.

@wata727
Copy link
Contributor Author

wata727 commented Jul 4, 2022

Fixed by 95a19ef

@wata727
Copy link
Contributor Author

wata727 commented Jul 6, 2022

UPDATE: Added visualize_records to visualize multiple histories a29288c

This is useful for checking at what time it is valid between different histories such as associations. In the example below, it's easy to see that a department is valid in the employee's three histories.

irb(main):006:0> puts ActiveRecord::Bitemporal::Visualizer.visualize_records(employees)

                        | 2021-08-16 18:12:30.748
                        |         | 2021-11-01 16:08:28.857
                        |         | | 2021-11-04 10:26:47.052
                        |         | |       | 2022-01-04 16:05:27.859
                        |         | |       |                   | 9999-12-31 09:00:00.000
2021-08-16 18:40:32.432 |---------------------------------------|
                        |                                       |
2021-11-01 17:17:18.658 |---------|-----------------------------|
                        |         |                             |
2021-11-04 11:23:40.352 |         |-|---------------------------|
                        |         | |                           |
2022-01-04 16:42:01.277 |         | |-------|-------------------|
                        |         | |       |                   |
                        |         | |       |                   |
                        |         | |       |                   |
9999-12-31 09:00:00.000 |---------|-|-------|-------------------|
irb(main):007:0> puts ActiveRecord::Bitemporal::Visualizer.visualize_records(employees, [department])

                        | 2021-08-16 18:12:30.748
                        |         | 2021-11-01 16:08:28.857
                        |         | | 2021-11-04 10:26:47.052
                        |         | |       | 2022-01-04 16:05:27.859
                        |         | |       |                   | 9999-12-31 09:00:00.000
2021-08-16 18:40:32.432 |---------------------------------------|
                        |                                       |
2021-11-01 17:17:18.658 |---------|-----------------------------|
                        |         |                             |
2021-11-01 17:17:21.558 |         |-----------------------------|
                        |         |*****************************|
2021-11-04 11:23:40.352 |         |*****************************|
                        |         |*****************************|
2021-11-04 11:23:43.763 |         |-----------------------------|
                        |         | |                           |
2022-01-04 16:42:01.277 |         | |-------|-------------------|
                        |         | |       |                   |
9999-12-31 09:00:00.000 |---------|-|-------|-------------------|

body.print("#{record.transaction_from.strftime('%F %T.%3N')} ", line: line)
if width > 0
if height > 0
body.print('|' + '-' * width + '|', line: line, column: column)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nits] Changing '|' to '+' will make it a little easier to see.
https://github.com/kufu/activerecord-bitemporal/pull/94/files#diff-69d7b91cda642b52f976ad3f279e8d9aeb47b167a299f8426a6fc5d535c3aecfR88

before

                        | 2019-01-10 00:00:00.000
                        |         | 2019-01-15 00:00:00.000
                        |         |         | 2019-01-20 00:00:00.000
                        |         |         |                   | 9999-12-31 00:00:00.000
2019-01-10 00:00:00.000 |---------------------------------------|
                        |                                       |
2019-01-15 00:00:00.000 |---------|-----------------------------|
                        |         |                             |
                        |         |                             |
2019-01-20 00:00:00.000 |         |---------|-------------------|
                        |         |         |*******************|
                        |         |         |*******************|
                        |         |         |*******************|
                        |         |         |*******************|
9999-12-31 00:00:00.000 |---------|---------|-------------------|

after

                        | 2019-01-10 00:00:00.000
                        |         | 2019-01-15 00:00:00.000
                        |         |         | 2019-01-20 00:00:00.000
                        |         |         |                   | 9999-12-31 00:00:00.000
2019-01-10 00:00:00.000 +---------------------------------------+
                        |                                       |
2019-01-15 00:00:00.000 +---------+-----------------------------+
                        |         |                             |
                        |         |                             |
2019-01-20 00:00:00.000 |         +---------+-------------------+
                        |         |         |*******************|
                        |         |         |*******************|
                        |         |         |*******************|
                        |         |         |*******************|
9999-12-31 00:00:00.000 +---------+---------+-------------------+

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed 9bafa9e

Copy link
Collaborator

@osyo-manga osyo-manga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Thanks!!

Copy link
Contributor

@sugamasao sugamasao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great works for visualization 🎉

LGTM 👍

@wata727 wata727 merged commit 00774c9 into kufu:master Jul 13, 2022
@wata727 wata727 deleted the bitemporal_visualizer branch July 13, 2022 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants