Bubble Validations

We were developing a Content Management System which is composed of different components, with each component having dependency on other components in the hierarchy.

Our CMS has the following structure :

  • Site has_many pages
  • Page has_many sections
  • Section has_many embedded_modules
  • EmbeddedModule has_many elements

Problem Statement

For any component to be publishable, we need to ensure that not only a particular component, but also its descendants(children) should satisfy the criteria of publishability.
In our case a Page could be publishable, if all sections belonging to it were publishable. A section would be publishable if all embedded modules belonging to it were publishable. A EmbeddedModule would be publishable if all elements belonging to it were publishable.

If at any step of the hierarchy the criteria fails, not only that component should be marked as unpublishable but also upchain should be notified of unpublishablity.
If any EmbeddedModule gets unpublished then it must unpublish the upper hierarchy i.e its associated section, page and site.

Our Solution

To achieve this we identified that a shared module would be needed which can handle publishability of a component and should also be able to notify the publishability change to its parent.

We built a ruby module called publishable.rb and included it in every model in the hierarchy. There are some pre-requisites for including this module

  • every AR model that includes it should have a table column (type: bool) named “publishable_flag”
  • model must implement method called check_publishing_rules that checks for publishability related validations and add any errors to publishing_errors instance provided by the module
  • (optional) implement a method called notify_publishability_upchain! that checks for publishability in the next model up in the hierarchy. Skip this for the top most model (root node)

Below is how one of our AR model implements the above methods on including Publishable module


class EmbeddedModule < ActiveRecord::Base
  include Publishable
  belongs_to :section
  has_many :element_types, foreign_key: :module_id
  has_many :text_elements, through: :element_types, source: :element, source_type: 'Text'

  # add errors to publishing_errors
  def check_publishing_rules
    publishing_errors.add(:base, "Must have at least one element") if elements.length.zero?
    publishing_errors.add(:base, "One or more elements are unpublishable") unless elements.all?(&:publishable?)

  def notify_publishability_upchain!
    section.notify_publishability_change! if section

See methods below that makes up the Publishable module

Publishable.rb (module)

Following method takes care of validating the publishability rules of the calling class, sets the flag and then repeat the steps for higher level in the hierarchy.

# set the publishability flag of the calling instance so that the instance remains in the consistent state whenever referred up or down chain.
# update publishability_flag in db
# bubble up the publishability check to parent
def notify_publishability_change!
  write_attribute(:publishable_flag, publishable?)
  ActiveRecord::Base.connection.execute("update #{self.class.table_name} set publishable_flag = #{publishable_flag} where id = #{id}")
  notify_publishability_upchain! if respond_to?(:notify_publishability_upchain!)

Methods below checks the publishability and report errors:

def publishing_errors
  @publishing_errors ||= ActiveModel::Errors.new(self)

def publishable?

def check_publishability

Finally to trigger the publishability check on any component just call the following method validate_publishability

def validate_publishability

So lets say to check the publishability of an EmbeddedModule instance simply call embedded_module.validate_publishability which will check its publishing_rules and trigger notify_publishability_upchain! on its parent i.e Section and bubbles up till it reaches the top node of the hierarchy.

You can grab the module from here Publishable.rb
Here is the working example for better understanding https://github.com/vinsol/publishability

Let us know if you know any good alternatives to this approach.

Leave a Reply

Your email address will not be published. Required fields are marked *