module Sequel module Plugins # The touch plugin adds a touch method to model instances, which saves # the object with a modified timestamp. By default, it uses the # :updated_at column, but you can set which column to use. # It also supports touching of associations, so that when the current # model object is updated or destroyed, the associated rows in the # database can have their modified timestamp updated to the current # timestamp. # # Since the instance touch method works on model instances, # it uses Time.now for the timestamp. The association touching works # on datasets, so it updates all related rows in a single query, using # the SQL standard CURRENT_TIMESTAMP. Both of these can be overridden # easily if necessary. # # Usage: # # # Allow touching of all model instances (called before loading subclasses) # Sequel::Model.plugin :touch # # # Allow touching of Album instances, with a custom column # Album.plugin :touch, :column=>:updated_on # # # Allow touching of Artist instances, updating the albums and tags # # associations when touching, touching the +updated_on+ column for # # albums and the +updated_at+ column for tags # Artist.plugin :touch, :associations=>[{:albums=>:updated_on}, :tags] module Touch # The default column to update when touching TOUCH_COLUMN_DEFAULT = :updated_at # Set the touch_column and touched_associations variables for the model. # Options: # * :associations - The associations to touch when a model instance is # updated or destroyed. Can be a symbol for a single association, # a hash with association keys and column values, or an array of # symbols and/or hashes. If a symbol is used, the column used # when updating the associated objects is the model's touch_column. # If a hash is used, the value is used as the column to update. # * :column - The column to modify when touching a model instance. def self.configure(model, opts={}) model.touch_column = opts[:column] || TOUCH_COLUMN_DEFAULT if opts[:column] || !model.touch_column model.instance_variable_set(:@touched_associations, {}) model.touch_associations(opts[:associations]) if opts[:associations] end module ClassMethods # The column to modify when touching a model instance, as a symbol. Also used # as the default column when touching associations, if # the associations don't specify a column. attr_accessor :touch_column # A hash specifying the associations to touch when instances are # updated or destroyed. Keys are association dataset method name symbols and values # are column name symbols. attr_reader :touched_associations # Set the touch_column for the subclass to be the same as the current class. # Also, create a copy of the touched_associations in the subclass. def inherited(subclass) super subclass.touch_column = touch_column subclass.instance_variable_set(:@touched_associations, touched_associations.dup) end # Add additional associations to be touched. See the :association option # of the Sequel::Plugin::Touch.configure method for the format of the associations # arguments. def touch_associations(*associations) associations.flatten.each do |a| a = {a=>touch_column} if a.is_a?(Symbol) a.each do |k,v| raise(Error, "invalid association: #{k}") unless r = association_reflection(k) touched_associations[r.dataset_method] = v end end end end module InstanceMethods # Touch all of the model's touched_associations when destroying the object. def after_destroy super touch_associations end # Touch all of the model's touched_associations when updating the object. def after_update super touch_associations end # Touch the model object. If a column is not given, use the model's touch_column # as the column. If the column to use is not one of the model's columns, just # save the changes to the object instead of attempting to a value that doesn't # exist. def touch(column=nil) if column set(column=>touch_instance_value) else column = model.touch_column set(column=>touch_instance_value) if columns.include?(column) end save_changes end private # The value to use when modifying the touch column for the association datasets. Uses # the SQL standard CURRENT_TIMESTAMP. def touch_association_value Sequel::CURRENT_TIMESTAMP end # Directly update the database using the association dataset for each association. def touch_associations model.touched_associations.each do |meth, column| send(meth).update(column=>touch_association_value) end end # The value to use when modifying the touch column for the model instance. # Uses Time.now to work well with typecasting. def touch_instance_value Time.now end end end end end