Skip to content

BadrIT/persistize

 
 

Repository files navigation

Persistize

Persistize is a Rails plugin for easy denormalization. It works just like memoize but it stores the value as an attribute in the database. You only need to write a method with the denormalization logic and a field in the database with the same name of the method. The field will get updated each time the record is saved:

class Person < ActiveRecord::Base
  def full_name
    "#{first_name} #{last_name}"
  end

  persistize :full_name
end

...

Person.create(:first_name => 'Jimi', :last_name => 'Hendrix')
Person.find_by_full_name('Jimi Hendrix') # #<Person id:1, first_name:"Jimi", last_name:"Hendrix", full_name:"Jimi Hendrix" ...>

Dependency

Sometimes you want to update the field not when the record is changed, but when some other associated records are. For example:

class Project < ActiveRecord::Base
  has_many :tasks

  def completed?
    tasks.any? && tasks.all?(&:completed?)
  end

  persistize :completed?, :depending_on => :tasks

  named_scope :completed, :conditions => { :completed => true }
end

class Task < ActiveRecord::Base
  belongs_to :project
end

...

project = Project.create(:name => 'Rails')
task = project.tasks.create(:name => 'Make it scale', :completed => false)    
Project.completed  # []

task.update_attributes(:completed => true)
Project.completed  # [#<Project id:1, name:"Rails", completed:true ...>]

In the previous example, project was depending on task for calculating Project#completed. But this will call Project#completed with every create, update or destroy for any task. For more performance optimization, you may want to recalculate Project#completed in some certain cases.

For example, in the previous example, if the project was uncompleted and you created a new uncompleted task, you don’t have to recheck if project will be completed or not.

You can add dependency as a hash, in Rails 5:

class Project < ActiveRecord::Base
  has_many :tasks

  def completed?
    tasks.any? && tasks.all?(&:completed?)
  end

  # update project completed status only if the new/updated task status is different than the project status
  # if you didn't add save or destroy Proc it will be evaluate to true by default
  persistize :completed?, depending_on: { tasks: {
      save: Proc.new{ |project, task| project.completed != task.completed? },
      # destroy: Proc.new{ |project, task| any_condition },
      # when: Proc.new{ |project, task| any_condition }
    }
  }

  scope :completed, -> { where(completed: true) }
end

class Task < ActiveRecord::Base
  belongs_to :project
end

...

project = Project.create(name: 'Rails')
task = project.tasks.create(name: 'Make it scale', completed: false)    
project.completed  # false and no need to recalculate project status

task.update(completed: true) # now project completed status will be recalculated
Project.completed  # [#<Project id:1, name:"Rails", completed:true ...>]

You can use save, destroy and when procs.

  • Save: will be checked with each task after_save

  • destroy: will be checked with each task after_destroy

  • when: will be checked with each task save OR destroy

You can add more optimization by checking only if Task#completed changed. So any other change in task subject or any other attribute does NOT affect Project#completed

persistize :completed?, depending_on: { tasks: {
    save: Proc.new do |project, task| 
      project.completed != task.completed? && (task.new_record? || task.saved_change_to_completed?)
    end
  }
}

You can add more than one dependency using an array:

persistize :summary, :depending_on => [:projects, :people, :tasks]

These examples are just some of the possible applications of this pattern, your imagination is the limit =;-) If you can find better examples, please send them to us.

Install

Just add it to your Gemfile:

gem "persistize"

And run:

$ bundle install

To-do

  • Make cache optional (cache can cause records to be inconsistent if changed and not saved so it would be nice to be able to deactivate it)

Copyright © 2008-2014 Luismi Cavallé & Sergio Gil & Paco Guzmán, released under the MIT license

About

Easy denormalization for your ActiveRecord models

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Ruby 100.0%