Closed joemasilotti closed 1 year ago
Having done this before, I can tell you it's good that you're doing it now rather than later :)
You hit most of the points I'd suggest. A couple of other things to consider:
I'm not an expert but for an app I'm working on I used the following configuration:
Basically Accounts and Users are associated through a Membership model. This way a User can have multiple accounts and an account can have multiple owners or other roles.
You still have to ensure that the last owner on the account is not deleted before the account itself but apart from that this setup should cover all the good points from @subdigital (I guess).
class Account < ApplicationRecord
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships
accepts_nested_attributes_for :memberships
end
class User < ApplicationRecord
has_many :memberships, dependent: :destroy
has_many :accounts, through: :memberships
end
class Membership < ApplicationRecord
belongs_to :account
belongs_to :user
accepts_nested_attributes_for :account
enum :role, { owner: 0, admin: 1, user: 2 }, suffix: true, default: :user
end
When a user signs up a default account is created through a new membership
@user.memberships.build(role: :owner).build_account(name: @user.name)
Perhaps this ~30 line multi-tenant strategy can help?
For interacting I'm using this exact strategy with some minor adjustments. It's working great!
Having an additional query (user.account.object) every time we need data feels like it could be bad, but I might be pre-optimizing.
This can be solved with Current
. You check if the user can access the account once and save it in Current.account
.
This is slightly off-topic. I'm not sure if this is helpful, but I've had a lot of success using data_migrate when needing to add/change/remove data in rows. It seems like closing this issue will require changing some data, so I figured I'd toss it out there as a potential nice solution. I'm not sure how larger rails apps handle these situations, but, in my research, there wasn't a clear happy path.
Thanks for the feedback and links everyone!
At this point I'm leaning towards building out all of the modeling for Accounts but hiding everything from the user. Meaning, when you sign up you are assigned an account and all permissions and subscriptions are done via accounts. I can launch that and ensure nothing broke.
Then, once that is live, I can start layering in the UI needed to flesh out the actual feature. Most of what @subdigital is talking about, but limited to specific use-cases for businesses. (I don't ever see a developer needing to manage multiple profiles.)
@Tonksthebear, OK humor me. What's so bad about running data migrations inside the database migration? I've done it before without issue but I've heard it's not a great practice. Would love to know more and that gem looks like a great solution.
More concretely, a series of PRs could be as follows:
Account
Developer
and Business
from User
to Account
Conversation
and Message
from User
to Account
pay_customer
from User
to Account
User
records get their own Account
Account
when creating each Notification
user_id
-> account_id
on Developer
, Business
, Conversation
, and Pay::Customer
User
their own Account
Account
to every Notification
Business
and Developer
current_account
helperConversation
and Message
Business
name in top to developer/businesses/:id
UI for account management
Handle the rest via the console!
@Tonksthebear, OK humor me. What's so bad about running data migrations inside the database migration? I've done it before without issue but I've heard it's not a great practice. Would love to know more and that gem looks like a great solution.
I'm not sure if I can give good examples of how it is BAD per se. I like keeping them separated because I like the logical separation of schema modifications and data modifications. I also thought it would be risky to have a migration file potentially manipulate data based on old model validations (could prevent a successful migration on a new environment). I like the guarantee that schema migrations will ALWAYS work on a new environment, whereas issues with the data migrations could be sorted out separately. I might be overlooking an obvious way to handle that scenario though.
I also like intentionally making data changes with a separate command. Schema changes can obviously also affect data, but I know the really serious changes will require rails data:migrate
.
Let me know if you have any other questions. I've only ever worked solo, so I don't have knowledge other people might have on the subject, especially with larger databases. This is just the logic I ended up using 👍
Makes sense to me – thanks for the brain dump!
@jayeclark, can you please comment on this issue so I can assign it you?
👋
Dropping some thoughts here now that I've started messing with actual code... I've tried (very loosely) sketching out my initial approach a bit below.
In the initial set of work, a Business will initially still belong to a User and a User will only be able to own one Business. However, there will be an additional class UserRoleAtBusiness linking users to an existing Business and granting them permissions to initiate conversations on the Business' behalf, to invite other users or converse with candidates. Whether or not you can speak on behalf of a business will be controlled by this relationship. There will be a cap on user roles per Business based on subscription tier, and a user can only have one role per Business. If you are a user associated with a business, but are not the owner, you can view the business profile (from the dropdown on your avatar) but cannot edit it.
classDiagram
User -->DeveloperProfile:has one<br>
User -->Subscription:has one active?
User-->Business: has one<br>
Subscription-->Business:Determines<br>permissions for
class Business{
user_id
company
website
bio
developer_notifications
contact_name
contact_role
}
class DeveloperProfile{
user_id
name
hero
bio
website
linkedin
github
twitter
search_status
role_type
role_level
available_on
}
classDiagram
User -->UserRoleAtBusiness:has many
User -->DeveloperProfile:has one
User -->Subscription:has one active?
User-->Business: has one
UserRoleAtBusiness <-- Business: has many
Subscription-->Business:Determines<br>permissions for
class UserRoleAtBusiness{
role_type
entity_id
user_id
contact_name
contact_role
}
class Business{
user_id
company
website
bio
developer_notifications
send_notifications_to
}
class DeveloperProfile{
user_id
name
hero
bio
website
linkedin
github
twitter
search_status
role_type?
role_level?
available_on?
}
role_type
= member) and create owner records for all existing businessesThis looks like a great start, thanks for kicking it off!
There's one thing that doesn't seem to be addressed. If a business account owns a conversation, what happens when a second user (associated with said business) tries to respond? Should it look like it came from the original business sender? Or do we need a way to differentiate in the UI who sent the business (like Airbnb).
Continuing from 4. above, I'm having those scary second thoughts of rethinking the entire domain modeling. :)
As you mentioned, should a user have a profile not tied to a business or developer? Maybe that's where the avatar lives? We could migrate the relevant data from Developer
and Business
to the UserProfile
(or whatever we call it). Then Developer
becomes DeveloperProfile
and Account
could have a delegated type that includes the developer or business.
Sorry to derail but I think I need to think on this a bit more. This thread plus the Twitter convo with @kaspth really has my head spinning (in a good way!).
@joemasilotti Yeah, I’ve been moving slowly on this not so much because of unfamiliarity with Rails, but more that the more I think it through, the more I see other areas of the codebase that may need to change or be thought through first.
Conversations is a big one. Currently (to avoid blowing up the codebase) I’m conceiving of it that a business owner can see all the conversations but would need to actively take over a conversation they didn’t initiate in order to message the developer. (And it would be clear that someone new at Business XYZ was sending the message.) The conversation would still be visible to the original Business user up until the point it was taken over. If a user’s association with a business gets severed, they lose access to all conversations that they initiated on behalf of that business. I’m going to dive in to conversations this weekend and should have a few screenshots to share that can better communicate where my head is on this.
Wanted to give you a heads up that I took (yet) another spin on this Accounts vs. Users thing today. PR #508 has some scaffolding for the schema and data migrations on how to get us started in a slightly different direction.
Curious to hear your thoughts on that and how they align with your PR!
Hmm, at first glance it seems to be way overcomplicating things, so there must be something I'm missing. Will take a closer look later!
Ha, fair! I think the PR could lay a groundwork for something more robust than what we currently need. But will make it easier to extend than what we've been talking about.
I'm not sure if I'm overoptimizing or if it is thinking ahead.
OK, I took a closer look and it's more closely aligned than I initially thought. Definitely a better framework if it's likely that you'll want to extend account memberships to the developer side of things. And I can see the value of being able to switch account context - it feels slightly like overkill at the moment but will be increasingly useful as new features get added and there is more to 'do' in either context (right now if I'm a developer all I can really do in the app is update my profile and respond to messages, and if I'm a business I can search for/reach out to developers, update my profile, and respond to messages - it almost doesn't feel 'meaty' enough to be swapping between Developer & Business mode, if that makes sense?)
I left a comment in that other PR but the one thing that's still really tripping me up is the lack of a central user identity & profile that I retain as I switch context among these various accounts.
Right now, in the other PR, it feels like it's heading down the path of Twitter/Tweetdeck - ie you give others permission to post & DM on behalf of your twitter account, and when they do, you can't tell that it's not you tweeting... but this can also make it tough to track down bad actors and can be a jarring experience from a customer service perspective since the user may not be sure who they're DMing with at any given time.
An alternative framework would be something like AirBNB, where each person has a user profile that's extended by additional info based on whether they're in 'guest' or 'host' mode, and then within host mode they can be associated with and act on behalf of a number of different properties (as owner/host, co-host, manager, etc. - but in each conversation the guest will see that it is a specific person interacting with them, not just the business.)
Is either of those scenarios what you're going for @joemasilotti ? 😆
Thanks for calling this out! In the long run I'd like railsdevs to function more like Airbnb instead of Tweetdeck. But I'm not quite sure what the immediate next steps should be.
Could it be as simple (on the schema side) as making User avatarable and adding name fields to each user record?
(For existing users, name could be extracted from the Developer profile, and if there's no Dev profile then the name could be pulled from the business contact name.)
Sign up flow would probably need to change slightly, as would conversations.
That's a great call. We have all the data we need to backfill.
I love the idea of collecting name (or not) during sunup but having a new profile edit screen for name and avatar. It could even render above the developer/business form as nested attributes!
I think it might make sense to land that other PR and then have me change this one to focus on the user profile changes. It makes a lot of sense to me to solidify the concept of a user (that remains the same regardless of the context that user is operating in) & clean up how users are onboarded/represented in conversations before adding visible/perceptible complexity to the entities that any given user can be associated with. (But setting up the accounts framework behind the scenes first would make it a lot easier to migrate conversations.)
I could revert my changes in this PR so far and instead focus on those user data migrations once the accounts framework is set up.
What do you think?
It makes a lot of sense to me to solidify the concept of a user (that remains the same regardless of the context that user is operating in)
I agree. And I like where this conversation has navigated the decision.
I could revert my changes in this PR so far and instead focus on those user data migrations once the accounts framework is set up.
Your call! You are also welcome to contribute to the data migrations PR if you want to work on that.
Haven't forgotten about this! I thought about it some more and I don't think the user migration needs to wait for the accounts/data migration PR, and it might even be better if it happens beforehand. It probably makes sense to split the user work into two PRs:
PR 1: Basic user profile data migration (mainly backend + small signup flow UI tweak)
PR 2: UI and additional backend tweaks
I'm going to revert the existing commits on PR #490 & start adding new ones over the weekend on this new tack, but let me know if anything jumps out in the list above as immediately 'off' or overlooked @joemasilotti .
This sounds like a great plan, thank you for outlining it! Just a few questions/concerns:
Yeah, I was imprecise in my language. I should have written "add name and avatar to onboarding flow", not necessarily the initial sign up form. I'm leaning toward a nested form in both the developer and business flow that only appears if the info has not yet been provided. (So, if I sign up as a business and then later go to create a developer profile, I will not be prompted for my name & avatar, since I already provided those in the business onboarding flow. However, there will be an option in the signed in user component where I can update my name & avatar if I so desire.)
Yes, after thinking about it a bit and poking around in the code, it seems like the most important changes to conversations, messages, and notifications to separate users from the Developer or Business profiles they're acting on behalf of, can be done without any of the account scaffolding in place, and shouldn't be affected by how the account scaffolding eventually shakes out. Some additional changes will need to be made along with the Account-related code (adding a check that a given user has permission to view a conversation they were participating in, ie that particular user has an active relationship with the developer or business account) but these are additive and wouldn't affect the changes I'm proposing, I don't think.
One more question: if a business goes to message a developer they won't have an avatar until they manually set one. Should we fallback to the business "logo" or force them to add a new avatar?
Hi @joemasilotti .. As discussed, I would like to work on this issue. Will go through current comments and come up with details to get this working.
Closed in favor of a fresh start in #843.
Well, it was bound to come eventually! A potential customer expressed interest in needing multiple users per paid account. I'm still working through how this will all work, so this comment will serve as a living doc of how things will be handled.
Proposal
Account
model with anowner_id
column,User
account_id
toUser
model,belongs_to: :account
user_id
onBusiness
andDeveloper
toaccount_id
owner
attribute on the account to the usercurrent_user
tocurrent_user.account
#current_account
, inApplicationHelper
orApplicationController
PaywalledComponent
,DeveloperPrimaryActionComponent
Open questions
pay_customer
expects an email and name, but this will live onAccount
now. How can the owner's details be passed to/from Pay?user.account.object
) every time we need data feels like it could be bad, but I might be pre-optimizing.Perhaps this ~30 line multi-tenant strategy can help?