Skip to main content

Validating Nested Models

What is validation ?

Validations are required for ensuring that the received data is correct and valid. If the received data is valid then we do the further processing with the data.

Nested Model Validation

Nested model is sometimes tricky when you have complex associations. Here I'm explaining how we can do nested model validation.

We use nested form to save associations through its parent model.

Generally we do:

class Album < ActiveRecord::Base
  has_many :songs

  accepts_nested_attributes_for :songs
end

When we are using nested form we want to save a new or edit an existing album with songs.

Now suppose we want all the songs to be removed on removal of the album.

class Album < ActiveRecord::Base
  has_many :songs

  accepts_nested_attributes_for :songs, :allow_destroy => true
end

At this point we can detect and reject empty association entries

class Album < ActiveRecord::Base
  has_many :songs

  accepts_nested_attributes_for :songs,
                                                 :allow_destroy => true,
                                                 :reject_if => proc {|attrs| attrs['name'].blank? }
end

The unhappy cases start here If you also want to validate that you always have at least one associated model. We can do the following, which is close, but does not work in all situations:

class Album < ActiveRecord::Base
  has_many :songs

  accepts_nested_attributes_for :songs,
    :allow_destroy => true,
    :reject_if => proc {|attrs| attrs['name'].blank? }

  validate :must_have_one_song

  def must_have_one_song
    errors.add(:songs, 'must have one song') if songs_empty?
  end

  def songs_empty?
    songs.empty?
  end
end

This works except when we are destroying an associated model; the destroy occurs after the validations have been run, making songs_empty? true. The fix is to check the associated models to see if they are marked_for_destruction during the save, like so:

class Album < ActiveRecord::Base
  has_many :songs

  accepts_nested_attributes_for :songs,
    :allow_destroy => true,
    :reject_if => proc {|attrs| attrs['name'].blank? }

  validate :must_have_one_song

  def must_have_one_song
    errors.add(:songs, 'must have one song') if songs_empty?
  end

  def songs_empty?
    songs.empty? or songs.all? {|song| song.marked_for_destruction? }
  end
end

Now the validation will fail as expected. We might check the associated models for destruction separately to generate a more appropriate message.

Comments

Popular posts from this blog

3 Dimensions to Scaling: The scaling cube

Source: https://thenewstack.io/from-monolith-to-microservices/  X-axis scaling Running multiple copies of an application behind a load balancer. Each copy can handle 1/N of the load. Where N is the number of running copies. This is simple and commonly used approach to scale. Cons * Each copy accesses all the data, cache size will be higher. * Doesn't solve increasing development and application complexity. Y-axis scaling Splitting the application into multiple, different services. Then more infra resources can be added to only the micro-service which is bottleneck in the architecture. Here cached can be utilized efficiently, only where it is needed. Z-axis scaling Each server runs same copy similar to X-axis scaling. The big difference is that each server. Some component of the system is responsible for routing each request to the appropriate server. This is commonly used to scale databases where data is partitioned(a.k.a sharded) Pros * Each server

Reactive Microservices with Vert.x(Vertx.io)

Vert.x is an event driven and non blocking “Platform” where we can develop our application on. Vert.x is like Node.JS, but the biggest difference is that it runs on the JVM, it’s scalable, concurrent, non-blocking, distributed and polyglot. Following four key properties of Vert.x helps us in developing reactive applications. Async and non-blocking model Elastic Resilient Responsive Async and non-blocking model None of the Vert.x APIs block the calling thread(with few exceptions). If the result can be found quickly, It will be returned; Otherwise it will be handled by a handler to receive event sometime later. vertx      .createHttpServer()      .requestHandler(r -> {            r.response()            .end("<h1>Hello from my first " + "Vert.x 3 application</h1>"); })            .listen(8080, result -> {                 if (result.succeeded()) {                      fut.complete();                 } else {