-
Notifications
You must be signed in to change notification settings - Fork 8
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
change: Don't require specifying update type for watchers with labels #19
change: Don't require specifying update type for watchers with labels #19
Conversation
0deb8ab
to
1de2a78
Compare
Would love feedback from recent contributors @frerich and @barrelltech on this since it's a breaking change |
1de2a78
to
cf004f8
Compare
Something the occurs to me that maybe should happen with this update. When not using a label, perhaps the schema module / update type should be combined in a tuple so that the message is always a 2-element tuple:
🤷 ? This would also mean that the messages would jive with how the |
b93cb10
to
1699363
Compare
I like the idea of repeating the schema/action tuple used in the watchers in the message, i.e. if you have
then you would get calls to def handle_info({{MyApp.Accounts.User, :updated}, _columns}, socket) do
Similarly, when specifying a schema/table manually as in
it would result in message to def handle_info({%{table_name: "comments", :updated}, _params}, socket) do
In fact, would this remove the need to have a notion of labels completely? When would you ever specify a label if this is possible? |
The thing is that somebody could specify two conflicting table definitions, like: {
%{
table_name: "comments",
primary_key: :comment_id,
columns: [:title, :body, :author_id, :post_id],
association_columns: [:author_id, :post_id]
}, :updated, extra_columns: [:post_id]
},
{
%{
table_name: "comments",
primary_key: :the_id,
columns: [:title, :description, :author_id, :post_id],
association_columns: [:author_id, :post_id]
}, :updated, extra_columns: [:post_id]
} The I feel like labels are quite important. Firstly they are required by {MyApp.Accounts.User, :updated, trigger_columns: [:email, :phone], label: :user_contact_info_updated},
{MyApp.Accounts.User, :updated, trigger_columns: [:password_hash], label: :user_password_updated}, But even in cases where {MyApp.Accounts.User, :updated, extra_columns: [:email, :phone], label: :user_updated_with_contact_info},
{MyApp.Accounts.User, :updated, trigger_columns: [:name, :role], label: : user_updated_with_identifying_info}, This way different processes could subscribe to updates without having everything in the messages. |
That's a good point, I didn't consider that!
Here's a crazy idea: how about aligning the configuration and the message format such that the entire table definition as written in the Each message sent by ecto_watch could be a 3-tuple consisting of
E.g. given a table definition (based on an Ecto schema) as in watchers: [
{MyApp.Accounts.User, :inserted}
] The -def handle_info({:inserted, MyApp.Accounts.User, %{id: id}}, socket) do
+def handle_info({:ecto_watch, {MyApp.Accounts.User, :inserted}, %{id: id}}, socket) do With this change, more complex table definition lists like in your example: {
%{
table_name: "comments",
primary_key: :comment_id,
columns: [:title, :body, :author_id, :post_id],
association_columns: [:author_id, :post_id]
}, :updated, extra_columns: [:post_id]
},
{
%{
table_name: "comments",
primary_key: :the_id,
columns: [:title, :description, :author_id, :post_id],
association_columns: [:author_id, :post_id]
}, :updated, extra_columns: [:post_id]
} ...would naturally work the same way, the second tuple element of the def handle_info({:ecto_watch, {%{table_name: "comments", primary_key: :comment_id}, _action, _opts}, columns}, socket)
def handle_info({:ecto_watch, {%{table_name: "comments", primary_key: :the_id}, _action, _opts}, columns}, socket) If there is just one table definition, no matter how complicated, users could even just use def handle_info({:ecto_watch, _table_def, columns}, socket) I.e. the value of the table definition tuple is used as the 'name' of the watcher, users can pattern-match in as much detail as needed to disambiguate. This might remove the necessity for a 'label' notion -- and it would also align the order of values in the message with what's used in the configuration: right now, in the configuration, you would do watchers: [
{MyApp.Accounts.User, :inserted}
] but in the message, the order of schema name and action is reversed: def handle_info({:inserted, MyApp.Accounts.User, %{id: id}}, socket) do
I.e. whatever users put into the configuration, they'll get the same thing in the message. |
Honestly this just makes me think that we should have stuck with requiring users to define an Ecto schema module rather than allowing general table definitions 😅 That doesn't mean that I think the feature should be removed! Just that I think it's important to stick to simple things to match on. I've created a new discussion about this so that this PR doesn't go off-topic. I've also created a separate discussion regarding the idea of having While the ideas are somewhat related (because of the idea of labels, for one thing), I'd like to keep this PR's thread focused on the question at hand: Combining the watcher identifier and update_type into a single element which is sent as a message. So for example this basic watcher definition: {MyApp.Accounts.User, :inserted}, Is handled in this way: def handle_info({:inserted, MyApp.Accounts.User, %{id: id}}, socket) do
However if you define watchers with the {MyApp.Accounts.User, :updated, trigger_columns: [:email, :phone], label: :user_contact_info_updated},
{MyApp.Accounts.User, :updated, trigger_columns: [:password_hash], label: :user_password_updated}, But labels can be used in general: {MyApp.Accounts.User, :inserted, label: :user_inserted},
{MyApp.Accounts.User, :updated, label: :user_updated}, It maybe doesn't make as much sense to do it in the above case, but see the examples above using Anyway, the main point (sorry for taking so long to get there), is that when you have a message for a label like from the above the label represents the configuration of the schema, update_type, extra_columns, etc... and I think it doesn't make sense to have both the def handle_info({:inserted, user_inserted, %{id: id}}, socket) do
def handle_info({:updated, user_updated, %{id: id}}, socket) do I think that, when using labels, the def handle_info({user_inserted, %{id: id}}, socket) do
def handle_info({user_updated, %{id: id}}, socket) do However... That means that in the more "normal" case, you might have a three-element tuple: def handle_info({:inserted, MyApp.Accounts.User, %{id: id}}, socket) do
So I would propose changing this PR so that the first element is either a tuple with schema/update_type (note the flipping in order from the way messages are currently formed!) OR a label: def handle_info({{MyApp.Accounts.User, :inserted}, %{id: id}}, socket) do
# OR, if the `label` option is used:
def handle_info({:user_inserted, %{id: id}}, socket) do Just noticed this bit from you:
watchers: [
{MyApp.Accounts.User, :inserted}
]
def handle_info({:inserted, MyApp.Accounts.User, %{id: id}}, socket) do
Yeah, I agree that it should be the same way around. That was an oversight on my part 😅 That's what I was aiming for with this PR + the additional change I'm proposing. |
9626a49
to
b30bdc2
Compare
b30bdc2
to
d607d39
Compare
Decided to use tuples for subscribe and for message and switched the order to have the schema first. Changed the |
So if you have a watcher using the
label
option like this:Before you would have:
Now you don't have the update type (
:inserted
/:updated
/:deleted
) because the label is the fully identifying piece of information:Labels can be used whenever, but they are required when using the
trigger_columns
option or when specifying a map instead of an ecto schema (just added in #18)