Reputation: 1455
I'm using the Null Object pattern in my Rails application to implement the concept of a guest user account.
Like many apps, I have a method on ApplicationController
called current_user
.
In the case of a non-logged in user, I want to use my guest user null object.
It works in many cases, but then there run into something like the following -
params.merge({ user: current_user })
MyModel.new(params)
Of course this fails, with the following exception.
ActiveRecord::AssociationTypeMismatch: User expected, got GuestUser
My question is, what is a way to elegantly handle this kind of case. The idea for the Null Object pattern is that you can transparently swap in this null object and have it essentially be a duck type of the real object.
It's obvious how to do that for methods being called on the object, but in this case, I want to be able to pass this in and basically have it set the association column to null
, rather than needing a whole bunch of custom logic (avoiding that is the whole point of the null object pattern anyway).
A polymorphic relation isn't quite it.
Upvotes: 1
Views: 1344
Reputation: 1810
I had a similar problem. I just went from assigning the object to assigning the object.id
which I set to nil on the Null Object. It is kind of a hack I think though.
Upvotes: 0
Reputation: 14851
One option would be to override the user=
method, so that it's aware of the existence of GuestUser
(and can handle appropriately):
def user=(value)
if value.is_a?(GuestUser)
super(nil)
else
super
end
end
All mass-assignment methods in Rails (create, update, etc.) will use the appropriate setter to set the value. This can be easily be put into a concern if this is a common pattern in your application.
If you don't allow nil
in the user_id
column you have the flexibility to do something like assign a sentinel value, which you could then use in your accessor as well:
def user
if user_id == GUEST_USER_ID
GuestUser.new
else
super
end
end
Upvotes: 0
Reputation: 102134
Using the null object pattern here is definatly not a good idea since you need database generated ids to build associations and maintain referential integrity if you intend the user to have any kind of persistence before "registering".
Allowing a MyModel
to be created without a user_id would essentially create an orphaned record and just gives you another problem of linking it to user behind the screen. Thats why your schema should not allow it in the first place.
Rather you want to create the guest user record when needed (like when a guest user adds the first item to a cart) and use a recurring task (like a Cron tab) to periodicaly clean out junk records.
I would also consider if you really want to setup guest users as a seperate class since STI and polymorphism tends to get really messy when joining. Just use a timestamp column (records when the account was activated) or an enum instead.
Upvotes: 0
Reputation: 513
Quick answer: No such thing as an elegant way to handle that (I'm not sure how elegance is quantified).
You'll have to create a concern that mimics the persistence methods of the model from which your null object is based on (User). You'll also have to write methods to appease ActiveRecord to make the associated column be nil
.
Fortunately for you, this use-case has been solved
Upvotes: 2
Reputation: 586
if your MyModel accepts null for user_id, then you can do
params.merge(user: current_user) unless current_user.is_a?(GuestUser)
MyModel.new(params)
Upvotes: 0