Handling associations w/ null objects in Rails - ruby-on-rails

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.

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

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)

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.

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

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.

Related

access ActiveRecord Has Many associations

I have a model which has a lot of associations. What I need to do is to check whether one of those associations is not present or all of them are set correctly.
To do so, I created an array which includes all of those needs-to-be-checked fields. And created a loop through each element, but the case is that I can not access has_many related attributes with read_attribute method. It basically returns nil whenever I try to access has many associated fields.
What I am trying to do, I can access all related objects via car.drivers but I can not access the same object with car.read_attribute(:drivers) (because some of them are attributes and some are relations)
I think it's the behavior of read_attribute, so what should I use to access any (attribute or relation) on ActiveRecord object?
Regarding to the comments, it looks like no one understand what I am trying to do. I want to access the relations of one ActiveRecord object such like;
RELATIONS.each do |relation|
puts "#{relation} exists" if #object.relation.present?
end
What I do not know about this, is there any method that I can access the related objects with their string typed name. Similar to, #object.read_attribute(:attribute_name) In that way I can create a for loop, but for relations not for the attributes
To do so, I used dynamical method call. Below is an example of showing it
RELATIONS.each do |relation|
puts "#{relation} exists" unless #object.send('relation').nil?
end

Saving record fails due to uniqueness conflict with itself?

I have a procedure which receives two models, one which already exists, and another one which holds new attributes which I want to merge in the first one.
Since other parts of the program are holding the same reference to the new model, I can't just operate on the existing one. Therefor I do the following:
def merge(new_model, existing_model)
new_model.attributes = existing_model.attributes.merge(new_model.attributes)
new_model.id = existing_model.id
end
Now the new_model is being saved which gives me the uniqueness erorr (even though it's technically the same model). I also tried using the reload method, but that yields the same result.
Background:
The method above is run in a before_add callback on an association. I want to be able to call update on a model (with nested associations) without having to specify IDs of the nested models. This update is supposed to merge some associations, which is why I try to do the whole merge thing above.
You can't set the id of a model and then save the record expecting the id to be set since the id is the primary key of the database. So you are actually creating a whole new record and, thus, the uniqueness validation error. So you'll need to think of some other design to accomplish what you are wanting. It may help to know that what you are trying to do sounds similar to a deep_dup, except that ActiveRecord doesn't define this method (but Hash does).

what is the difference between an object_id, and id?

for any model instance, there is an #object_id and #id - I know that they are not the same.
However, I'm not quite sure what makes them different and how they would each be used in context.
Please help clear this up!!
Thanks!
In Rails, an ActiveRecord model instance has an id property that maps to the value stored in the id column of the database. This may be nil if the record hasn't been saved.
In Ruby, object_id is a value that represents the identity of the object in question. It is always populated with something since everything in Ruby is an object.
These two are not related. There may be several independent instances of a model, each with their own object_id value but an identical id.
If two variables refer to something with the same object_id, then they refer to exactly the same object.
It's rare to see object_id used in code, it's a Ruby internal that's hardly ever needed. Mostly it's to establish if you're talking about identical objects, or just equivalent ones.
You will, on the other hand, see id and similar values used frequently since that's the glue that holds your relational database together.
Everything (and i mean everything) in Ruby is an object. Each of these objects has an object_id, which is a value used to track them in memory, basically.
In Rails, model instances are automatically set up with methods to return their value from the corresponding column in the database. .id is one of these.
As far as using in context, generally you would not use object_id in your code, ever: it's an under-the-hood thing.
EDIT - as an aside, a common issue seen in older versions of ruby/rails (where the object_id method was actually called id, and was overridden by rail's id method) was caused by the fact that the object_id of nil is 4. So you would call id on a variable which you expected to be a model instance, but was actually nil, thus getting "4" back when you expected to get the id of a record from the database.
In short, :id is the default primary_key of a table.And object_id could be the foriegn_key unless you set a custom foreign_key.
For example,take these two table users and posts.The relation would be
user => has_many posts and
post => belongs to user
so in the posts table,we should create a foreign_key(in this case user_id) to make the relations works.
Hope it helps!
id is specific for ActiveModel record and it relates to id column in database. object_id is defined on Object and is unique for every single object created in the memory.

Pass object or id

This is just a question about best practices.
Imagine you have a method that takes one parameter. This parameter is the id of an object. Ideally, I would like to be able to pass either the object's id directly, or just the object itself.
What is the most elegant way to do this?
I came up with the following:
def method_name object
object_id = object.to_param.to_i
### do whatever needs to be done with that object_id
end
So, if the parameter already is an id, it just pretty much stays the same; if it's an object, it gets its id.
This works, but I feel like this could be better. Also, to_param returns a string, which could in some cases return a "real" string (i.e. "string" instead of "2"), hence returning 0 upon calling to_i on it. This could happen, for example, when using the friendly id gem for classes.
Active record offers the same functionality. It doesn't matter if you say:
Table.where(user_id: User.first.id) # pass in id
or
Table.where(user_id: User.first) # pass in object and infer id
How do they do it? What is the best approach to achieve this effect?
If the process is cross controller actions or in session, better to use id. For example, you are going to save a cart in session, the better choice is id. It's hard to watch how big an object is, using id will help performance and avoid unnecessary error.
However, if the methods are inside same request and all actions are within memory, using object itself would be quicker. For example, you pass an user to an authority class to check if he can do something. Because all objects are just a reference in memory, extra step to extract id is unnecessary and inefficient.
My guess is that ActiveRecord does something like this. Or, rather, that's how I'd do it.
def my_method(oid)
oid = oid.id if oid.respond_to?(:id)
# proceed
end

Force reload another model's methods in rails?

I have a model that defines methods based off of the entries in another model's table: eg Article and Type. An article habtm types and vice versa.
I define in Article.rb:
Type.all.each do |type|
define_method "#{type.name}?" do
is?(:"#{type.name}")
end
end
This works great! it allows me to ensure that any types in the type db result in the methods associated being created, such as:
article.type?
However, these methods only run when you load the Article model. This introduces certain caveats: for example, in Rails Console, if I create a new Type, its method article.type_name? won't be defined until I reload! everything.
Additionally, the same problem exists in test/rspec: if I create a certain number of types, their associated methods won't exist yet. And in rspec, I don't know how to reload the User model.
Does anyone know a solution here? Perhaps, is there some way to, on creation of a new Type, to reload the Article model's methods? This sounds unlikely.. Any advice or guidance would be great!
I think you'll be better off avoiding reloading the model and changing your api a bit. In Article, are you really opposed to a single point of access through a more generic method?
def type?(type)
return is? type if type.is_a? String # for when type is the Type name already
is? type.name # for when an instance of Type is passed
end
If you're set on having separate methods for each type, perhaps something like this would work in your Type class
after_insert do
block = eval <<-END.gsub(/^ {6}/, '')
Proc.new { is? :#{self.name} }
END
Article.send(:define_method, "#{self.name}?", block)
end

Resources