Represent multiple Null/Generic objects in an ActiveRecord association? - ruby-on-rails

I have a Casefile model that belongs_to a Doctor. In additional to all the "real" doctors, there are several generic Doctors: "self-treated", "not specified", and "removed" (it used to have a real doctor, but no longer does). I suspect there will be even more generic values in the future.
I started with special "doctors" in the database, generated from seed. The generic Doctors only need to respond to the name, title, company, published? methods.
This worked with one, was strained with two, and now feels completely broken. I want to change the behavior and can't figure out how to test it, a bad sign. Creating all the generic objects for testing is also trouble, including fake values to pass validation of the required Doctor attributes.
The Null Object pattern works well for one generic object. The "name" method could return "self-treated", as demonstrated by Craig Ambrose.
What pattern should I use when there are multiple generic objects with very limited state?

It seems to me like you could just add an extra field to the Casefile model called, say, treatment (which would be set to "self-treated", "not specified" etc.)
You could add a validation to ensure a Casefile either has a doctor or treatment assigned:
validate :has_doctor_or_treatment, :on => :save
def has_doctor_or_treatment
(self.doctor.exists? || !treatment.blank?)
end
Then you could use the treatment field to find Casefile's using .where:
Casefile.where(:treatment => "self-treated")
If you wanted, you could have treatment as an extra model, where Casefile has_one Doctor and has_one Treatment - but it seems like your needs are too simple to warrant that.

Related

Managing polymorphic data in Rails

I have an application where a User can create many Links, and each Link can store different type of data, depending on what type of Link it is. For example, a TelephoneLinkData stores a telephone number, an EmailLinkData stores an email address, a subject and a body. Each Link also has some fields in common, such as a reference to the user and a name.
I've tried to map this into ActiveRecord as cleanly as I can. Currently, I have a polymorphic relationship from Link to its data-type:
class Link < ApplicationRecord
belongs_to :user
belongs_to :link_data, polymorphic: true
...
class EmailLinkData < ApplicationRecord
has_one :link, as: :link_data
accepts_nested_attributes_for :links
...
Technically, I think this would be described as a reverse polymorphic relationship as instead of a class having possibly different parent classes, what I'm trying to model is a class having multiple possible different child classes. This works fine, and I'm able to create Links through the various *LinkData controllers, but what I'd really want to do is have the Link act as the primary source of interaction for the user, so that the user manages their links through the /links path. For example, I would like the API to allow a User to create a link by posting to /links with the data for the LinkData nested in the link_data field
I've looked around for other ways to model this relationship, and the most common other suggestion seems to be Single-Table Inheritance, but the majority of my columns will differ between LinkData classes, so that feels like the wrong abstraction.
Is there a more idiomatic way to model this data structure?
As is always the case, the best choice depends on the business or application needs, so it's difficult to provide a recommendation without knowing more about what you're trying to do.
It sounds like you prefer the MTI approach, essentially using actual foreign keys and an XOR constraint to the Link table instead of a type column. That's a totally reasonable (although not as common) alternative to a polymorphic association.
However, I think there was a bit of a misunderstanding in your question.
Technically, I think this would be described as a reverse polymorphic relationship as instead of a class having possibly different parent classes...
A polymorphic association in Ruby/Rails doesn't have anything to do with class inheritance (e.g. parents and children). You might be thinking of Single table inheritance. A polymorphic association allows one class (e.g. a Link) to be associated a record in any other table (e.g. the various classes of LinkData) via two fields, a association_id and association_type. These associated classes need not be related to each other. For example, a common use case might be the acts_as_commentable gem, that allows you to add a comment to any other object, and the comment would have a polymorphic association with the other classes.
In the second part of your question you mention that you'd like the User to interact with Link's via a single controller.
I would like the API to allow a User to create a link by posting to /links with the data for the LinkData nested in the link_data field
There's nothing stopping you from implementing this using the initially proposed data model. ActiveRecord may not handle this completely for you out of the box, but you can imagine implementing a link_data= method on the Link class that would create the appropriate associated object.
I'd say the pros/cons of using a polymorphic association would be...
Pros:
easy to setup and use
easy to make required (validate presence of / not null)
easy to associate with a new class
Cons:
no referential / database integrity
have to migrate data if you change a class name
And using the MTI approach is basically the opposite. A bit harder to setup and use, harder to add a new association/table, harder to ensure exactly one association exists... but the long term data quality benefits are significant.
I was able to get things to work the way I wanted to using multiple table inheritance, based largely on this chapter: https://danchak99.wordpress.com/enterprise-rails/chapter-10-multiple-table-inheritance/

Can I list all dependent objects of an object (say, user)?

I have a model and can manually check every (:has_many, :has_one) dependency, but I want some magic like current_user.attributes for records. So when I update model, I don't need to update method.
I tried Reflections, but it returns all theoretical dependencies/connections of model, isn't it? And I need dependent records from DB.
Something like X.where(user_id: #user.id) or #user.dependents
Is it possible?
You can assign required object to model and then
model.class.reflect_on_all_associations.map { |table| model.method(table.name).call }.select(&:any?)
For example:
user = User.first
user.class.reflect_on_all_associations.map { |table| user.method(table.name).call }.select(&:any?)
# returns all associated objects of first user
You can specify result using :has_many, :has_one or :belongs_to as argument of reflect_on_all_associations.
Possibly there is more elegant way, but it works.
TL;DR Don't do this :)
You can do something quite similar using reflections. For example
#user.class.reflections.keys.flat_map { |reflection| me.send(reflection) }
will give you an array with all the objects associated with the user. But what's next?
For almost any real-world logic around this list's members (except the basics that come from AR::Base) you will have to check either a class of an object or use bug-prone try magic - both options are reasonable trade-off sometimes, but in most practical cases I'd prefer less smelly solutions (even if they are a bit more verbose).

Organization model extends user model

I have User model and Organization model. The only difference is that organization has_many users, all other properties are same. I don't want to put it in one table/model. How can I remove tons of code duplicating in this models? Should I use Concerns? I think, it will be not normal if User model will looks like :
class User < ActiveRecord::Base
include user_concern
end
So, how can I extend user model in other model? And how to generate this model with rails g with all User's fields inside?
beware STI
I would keep with concerns rather than using STI. STI often causes more problem that it solves (type mismatches, form urls, etc), plus inheritance won't make sense, here : an user is not a kind of company, and a company is not a kind of user.
That's a naming problem
I think your problem is a naming one. Obviously, your concern should not be "UserConcern". The question is : what kind of methods do you group in that concern ?
Are your methods about social relation between users ? Then, you need a Socializable concern. Are they about subscribing to mailing list ? Then you need a Subscribable concern.
It's ok to have several ones, even if they have a single method in it, because developers won't wonder "what the hell does this do ?" if all concerns are correctly named.
What to duplicate anyway
You should also probably let class level method calls out concerns.
If it's ok for scopes to be embedded in concerns (after all, they resolve in method definitions), it feels less natural to me to put relations in there.
It's ok to duplicate #has_many :foos, we do it all the time in separate models, and it's already difficult enough to get an idea of table schema from a model without hiding more information.
You could use single table inheritance (STI) for this. To get it to work, your model needs a type-field of type string, in which ActiveRecord stores the actual type of your record. Then, you just extend your base model for this.
migration
add_column :users, :type, :string
models
class User < ActiveRecord::Base and class Organisation < User.
ActiveRecord will now fill your type-field with the model-name, and store both in your users table (since this is the one the organisation model is inheriting from).
Have a look at the according section on http://api.rubyonrails.org/classes/ActiveRecord/Base.html .
However, in your case, I'd create a base model, e.g. Address, and then extend User and Organisation from it, to maintain semantically correct models.

Rails nested form with uniquness condition

Rails 2.3.5, Ruby 1.8.7.
I have three models, Person, AcademicTerm, and PersonTermStatus.
class PersonTermStatus {
belongs_to :academic_term
belongs_to :person
validates_uniquness_of :academic_term_id, :scope => :person_id
# ...
}
class Person {
has_many :person_term_statuses
}
In a dynamic nested form for a Person record, I allow the editing of the person_term_statuses. But I get validation errors if the user does either of the following:
Deletes a status and creates a new one with the same academic term in the same change.
Swaps the academic terms between two existing statuses.
I understand why this is happening. In (1), the status marked for deletion is not actually deleted before validation of the new status's uniquness condition. In (2), the uniquness condition again is applied before any changes, and it finds another record with the same academic_term.
The problem is, I can't figure a way around this. Is there a known solution?
(My nested form implmenetation is currrently using pretty much exactly the technique from RailsCast [ Part I and Part II )
There is no workaround for this that I know of. However, you can add foreign keys to your database to enforce the uniqueness on the database side and then use the following approach.
Add a before_validation to the parent model that deletes and recreates as new records all the children. Then add a custom validation function that manually checks the children records for uniqueness based on what's in memory (rather than what's in the database).
The downsides to this approach include:
The children don't retain the same IDs.
The created timestamp changes.

Return Object After Validation Failure

Is there a method to save an object but return the object if it fails the validation_uniqueness_of for a given field? For example, I have the following:
class User
has_many :words
...
end
class Word
belongs_to :user
validates_uniqueness_of :title
...
end
And I have a situation where I want to either return the Word object if the user has already saved this word, or return the newly saved word object. I am finding myself checking if the word object exists through my own search and then performing a save if not:
existing_word = find_by_word_and_user_id(word, user)
!existing_word.nil? ? existing_word : user.words.create({:word => word})
But I feel like if the user does not have the word saved, rails is going to perform a redundant search for the title uniqueness validation after my check and I was wondering if I could just do the save and return the already existing object that rails finds for the validation.
You might be interested in #find_or_create_by_word_and_user, another of ActiveRecord's magic methods:
obj = Word.find_or_create_by_word_and_user(word, user)
# Preferred way
obj = user.words.find_or_create_by_word(word)
This will do 2 database requests anyway: there's no way around that. Also, if your Word instance has more validation that you've shown us, the create will fail, because #find_or_create_by does not allow passing more attributes.
For all the details on the dynamic finder methods, search for "Dynamic attribute-based finders" on http://api.rubyonrails.org/classes/ActiveRecord/Base.html.
You can DRY up that statement a little by just saying:
find_by_word_and_user(word, user) || user.words.create(:word => word)
you could eliminate a db hit by reversing these, where it tries to create before trying to find. But that wouldn't be a good idea, because there are other reasons the create might fail, so you can't be guaranteed it's the uniqueness issue.
You're not going to see a noticeable performance hit by just doing it the first way, I'd stick with that. Your only other option is to remove the validation itself, and use the statement above. But then you have to make sure that everywhere you create words, you're checking for uniqueness.

Resources