-
-
Notifications
You must be signed in to change notification settings - Fork 505
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
Feature tags step 1 #4884
base: main
Are you sure you want to change the base?
Feature tags step 1 #4884
Conversation
7fc06ef
to
50bea61
Compare
a0c0bb1
to
a5c8808
Compare
Requesting a review from you @cielf first as I want to make sure this implementation is functionally correct. |
I'm including in @dorner for parallel - and I might not get to it til the end of the week. |
@@ -44,7 +45,7 @@ def end_date_is_bigger_of_end_date | |||
return if start_date.nil? || end_date.nil? | |||
|
|||
if end_date < start_date | |||
errors.add(:end_date, 'End date must be after the start date') |
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.
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.
Unrelated change, so I can move it to another PR if needed.
# More concise test ("should") matchers | ||
gem "shoulda-matchers", "~> 6.2" |
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.
Moved this gem because otherwise rails g model
fails because shoulda-matcher
is needed for factory bot to create the spec factory files, so we need shoulda-matcher
in the dev environment as well.
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.
Looks pretty good at first go-through.
Sorry for the delay - I keep leaving this to last in my reviews because I have to think about it the most, and I always end up without enough time to address it :) Hopefully soon! |
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.
So happy this is getting started! I think we need to tweak the solution a bit though.
@@ -66,9 +69,9 @@ def show | |||
|
|||
def update | |||
@product_drive = current_organization.product_drives.find(params[:id]) | |||
if @product_drive.update(product_drive_params) | |||
@product_drive.attributes = product_drive_params.merge(tags: tags_from_params) | |||
if @product_drive.save |
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.
Is there a reason this pattern is different than in create?
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.
Not sure why i did that. Fixed in dbe77d7 to be more consistent with other update
actions.
app/models/concerns/taggable.rb
Outdated
private | ||
|
||
def set_organization_id(tagging) | ||
tagging.organization_id ||= organization_id |
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.
Why does the join table need an organization_id?
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.
Also this method is pretty broadly named for something that's going to be in a model... maybe rename it so it's specific to tagging?
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.
I think there might be a modelling issue here. It's the Tag that should belong to the organization, not the Tagging. If we want to show only product drive tags, that should be an enum on the Tag record, not the Tagging. Tagging should be a simple join table between a "taggable" and a Tag.
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.
Either the tags
table or the taggings
join table need a foreign key reference to an organization. Functionally I think either would work. To me, it makes more sense that a tagging belongs to an organization rather than a tag. Because whoever is doing the tagging will belong to a certain organization so a tagging without an organization doesn't really make sense, whereas a tag doesn't necessarily need to belong to an organization it could be used by multiple organizations. If taggings references the organization, then tags can be referenced between organizations and this reduces the number of tag records. Adding type
to the tag model also means more duplicates if for example there is a donation tag and a product drive tag both use the same tag name.
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.
I know it doesn't really matter but out of curiosity I checked and it looks like the acts_as_taggable_on gem also uses a similar schema.
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.
I think I see what you're trying to achieve with this implementation, but I don't think it squares up with my thoughts.
My main concern is opening up the ability for tags to be a managed resource. In other words, organizations should be able to do things like rename tags, archive tags, or create tags in advance (e.g. to present a curated list).
If we limit ownership to the join table, that means that we can only work with tags as they are defined in relation to actual existing objects. So things like renaming would actually mean somehow creating a new tag and doing a "find/replace" on every taggable in the database. Ensuring an existing tag doesn't show up in a list of possible tags would be very difficult if not impossible.
In general we also want to try to keep things multitenant - organizations shouldn't share records if at all possible. This is why they don't all have the same Item list.
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.
Ah I didn't know what was meant by organizations being able to manage tags. If those are the requirements, then this for sure is wrong schema.
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.
Will organizations be able to create tags of a certain type without it being used? Or change the type of a tag?
If so I guess type
should go on the tags
table
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.
I think the first case is a reasonable one.
@cielf do you want to weigh in - am I right in my assumptions here?
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.
Ok -- I'm going to muse a bit here. We definitely need to discuss this more before making any changes based on these musings.
What's in my head, here, certainly wasn't explicit in the issue. Nor was the idea of typed tags.
We want the tags to be a tool the banks can use for identifying groups of things that belong together that we haven't otherwise grouped for them.
The use case that prompted this is a "campaign" for product drives - the idea of linking product drives with a common theme, like Mothers' Day or Christmas. I could certainly see having distributions associated with either of those examples.
I can also imagine banks using this to tag things associated with particular grants -- purchases, eligible partners or partner groups, special items, distributions.
So, from a user's point of view, why do we have type associated with the tags, at least, yet?
app/models/organization.rb
Outdated
@@ -68,6 +68,9 @@ class Organization < ApplicationRecord | |||
has_many :purchases | |||
has_many :requests | |||
has_many :storage_locations | |||
has_many :taggings | |||
has_many :product_drive_tags, -> { merge(Tagging.by_type("ProductDrive")).distinct }, |
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.
So here this should be a simple has_many
instead of :through
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.
Is the refactor from 413a6e0 what you meant?
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.
Nope - see above. I meant moving organization_id
to tags
.
class Tag < ApplicationRecord | ||
has_many :taggings, dependent: :destroy | ||
|
||
scope :alphabetized, -> { order(:name) } |
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.
...and this should have the by_type
scope instead of Tagging.
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.
Moved by_type
scope to Tag in 413a6e0.
@@ -103,6 +107,7 @@ | |||
<td><%= product_drive.name %></td> | |||
<td><%= product_drive.start_date.strftime("%m-%d-%Y") %></td> | |||
<td><%= product_drive.end_date&.strftime("%m-%d-%Y") %></td> | |||
<td><%= product_drive.tags.map(&:name).sort.join(", ") %></td> |
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 feels like a method that should live on the Taggable concern.
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.
True. Fixed in 99f4f97.
spec/factories/tags.rb
Outdated
FactoryBot.define do | ||
factory :tag do | ||
name { "Holidays" } | ||
initialize_with { Tag.find_or_create_by(name:) } |
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.
I'm not the biggest fan of quietly changing this from "create new tag" to "find existing tag"... certainly not in the base factory. I'd rather have helper methods to work with this if 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.
True, I wanted to avoid issues from tags being created in tests with non unique names, but this wasn't the right way to achieve that.
Fixed in efd015f.
@@ -47,6 +47,23 @@ | |||
expect(response.body).not_to include(product_drive_path(product_drive_five.id)) | |||
end | |||
|
|||
it "allows filtering by tag" do |
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.
Should this be a shared example?
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.
Do you mean a shared example for filtering product drives? Or a shared example for filtering different resources by tags?
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.
Seems premature to me to create a shared example for filtering by tags, since only product drives have tags.
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.
But the whole point of the tag feature is to make it a generic way to attach tags to anything - we know we're not going to limit it to product drives.
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.
Ok then I will make it a shared example.
@dorner Refactored organization_id to the tags table. |
Resolves #4820
Description
Type of change
How Has This Been Tested?
Added new tests
Screenshots