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" ...>
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.
Just add it to your Gemfile
:
gem "persistize"
And run:
$ bundle install
-
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