3

Let's take two simple Rails models, Post and PostRevision.

class Post < ApplicationRecord
  # Attribute: Status (ex: draft, public, private, deleted)
  # Attribute: Views

  # An owner, likely a belongs_to relation. Perhaps a has_many relation.

  has_many :post_revisions
  validates :post_revisions, :presence => true

  has_many :comments

  has_many :votes

  # Delegation to most recent active / not deleted PostRevision for title,
  # body, tags to effectively expose these attributes
end

class PostRevision < ApplicationRecord
  belongs_to :post

  # Attribute: Title
  # Attribute: Body
  # Attribute: Status (ex: deleted / not deleted, perhaps just an int flag)
  # Attribute: Views

  # Attribute: Revision history notes / text.

  # If a Post has_many owners, an attribute for author/owner of revision

  has_many :tags
end

The default behavior of the "belongs to" association is to require that the associated record is present (that is, a PostRevision must have a Post - this can be disabled with an argument).

This model is conceptually true, however there's an problem that exhibits itself in test setup and controller actions - see this Stack Overflow question on "1-to-1 or more" relationships in SQL. You can't create a PostRevision until there's a Post, but you need a valid PostRevision to create a Post. I see two options - I can make the "belongs to" relation in the PostRevision optional. This will let me create a PostRevision first then a Post and associate them. Alternatively, I can remove the validation of the existence of a PostRevision on a Post. This would let me create a Post and then multiple PostRevision.

I'm leaning toward the second approach - remove the validation on Post. However, I can't actually put into words why this is the better option, other than it feels more right to create the Post and the things that cut across revisions and then the revision with the rest of the data.

Is there an alternative approach that I haven't considered? If not, is there some kind of rational justification to the approach that I'm leaning toward?

  • 1
    @CandiedOrange The PostRevision is only ever associated with one Post. It doesn't make sense to me to say that a PostRevision can suddenly belong to a different Post. As far as construction, the IDs are generated automatically when the object is saved and provide the foreign key relationship between a PostRevision and it's Post. – Thomas Owens Feb 22 '18 at 1:28
  • 2
    It also doesn't make sense for it to be associated with no post at all. But you're chicken and egg problem demands that something give. I'm submitting that you can also solve this with a known bad egg that stands in for the egg you'll associate with after it exists. – candied_orange Feb 22 '18 at 1:34
  • 1
    Why in the world must a post be revised? Your model doesn't seem to represent reality to me. – Loren Pechtel Feb 22 '18 at 4:35
  • 1
    @ThomasOwens removing the validation on the existence of a PostRevision seems more natural to me (from my very little understanding and knowledge of the domain). – Laiv Feb 22 '18 at 11:31
  • 1
    I think is a framework issue. If some hypothetical framework would support "one-or-more" relationsships, there would be no problem, right? I guess the reason for so many frameworks not supporting these kind of relationships is probably, they are not well supported by relational databases, and the cyclic nature of this model causes often further problems. – Doc Brown Feb 22 '18 at 12:11
1

The problem you are trying to solve is a nested form. Rails handles this with the accepts_nested_attributes_for class method. Basically, when you go to create a Post, you would also pass the nested attributes for the first PostRevision. Rails then creates all of the records together in a single transaction, allowing the PostRevision to be created before the validation on the Post executes.

Given a Post model with an author attribute, and a PostRevision model with post_id, title, and body attributes, here's what the code would look like:

class Post < ApplicationRecord
  has_many :post_revisions

  validates :post_revisions, presence: true

  accepts_nested_attributes_for :post_revisions
end

class PostRevision < ApplicationRecord
  has_one :post
end

Here's the command to create the Post and the first PostRevision together:

Post.create(
  author: 'Langston Hughes', 
  post_revisions_attributes: [
    {
      title: 'Dust and Rainbows',
      body: <<~POEM 
        Oh, God of Dust and Rainbows, 
        Help us to see
        That without the dust the rainbow 
        Would not be.
      POEM
    }
  ]
)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.