Skip to content
This repository
branch: opt_routes
Fetching contributors…

Cannot retrieve contributors at this time

file 167 lines (153 sloc) 4.879 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
require 'active_model/attribute_methods'
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/duplicable'

module ActiveModel
  # == Active Model Dirty
  #
  # Provides a way to track changes in your object in the same way as
  # Active Record does.
  #
  # The requirements for implementing ActiveModel::Dirty are:
  #
  # * <tt>include ActiveModel::Dirty</tt> in your object
  # * Call <tt>define_attribute_methods</tt> passing each method you want to
  # track
  # * Call <tt>attr_name_will_change!</tt> before each change to the tracked
  # attribute
  #
  # If you wish to also track previous changes on save or update, you need to
  # add
  #
  # @previously_changed = changes
  #
  # inside of your save or update method.
  #
  # A minimal implementation could be:
  #
  # class Person
  #
  # include ActiveModel::Dirty
  #
  # define_attribute_methods [:name]
  #
  # def name
  # @name
  # end
  #
  # def name=(val)
  # name_will_change! unless val == @name
  # @name = val
  # end
  #
  # def save
  # @previously_changed = changes
  # @changed_attributes.clear
  # end
  #
  # end
  #
  # == Examples:
  #
  # A newly instantiated object is unchanged:
  # person = Person.find_by_name('Uncle Bob')
  # person.changed? # => false
  #
  # Change the name:
  # person.name = 'Bob'
  # person.changed? # => true
  # person.name_changed? # => true
  # person.name_was # => 'Uncle Bob'
  # person.name_change # => ['Uncle Bob', 'Bob']
  # person.name = 'Bill'
  # person.name_change # => ['Uncle Bob', 'Bill']
  #
  # Save the changes:
  # person.save
  # person.changed? # => false
  # person.name_changed? # => false
  #
  # Assigning the same value leaves the attribute unchanged:
  # person.name = 'Bill'
  # person.name_changed? # => false
  # person.name_change # => nil
  #
  # Which attributes have changed?
  # person.name = 'Bob'
  # person.changed # => ['name']
  # person.changes # => { 'name' => ['Bill', 'Bob'] }
  #
  # If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt>
  # to mark that the attribute is changing. Otherwise ActiveModel can't track changes to
  # in-place attributes.
  #
  # person.name_will_change!
  # person.name << 'y'
  # person.name_change # => ['Bill', 'Billy']
  module Dirty
    extend ActiveSupport::Concern
    include ActiveModel::AttributeMethods

    included do
      attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
      attribute_method_affix :prefix => 'reset_', :suffix => '!'
    end

    # Returns true if any attribute have unsaved changes, false otherwise.
    # person.changed? # => false
    # person.name = 'bob'
    # person.changed? # => true
    def changed?
      changed_attributes.any?
    end

    # List of attributes with unsaved changes.
    # person.changed # => []
    # person.name = 'bob'
    # person.changed # => ['name']
    def changed
      changed_attributes.keys
    end

    # Map of changed attrs => [original value, new value].
    # person.changes # => {}
    # person.name = 'bob'
    # person.changes # => { 'name' => ['bill', 'bob'] }
    def changes
      HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
    end

    # Map of attributes that were changed when the model was saved.
    # person.name # => 'bob'
    # person.name = 'robert'
    # person.save
    # person.previous_changes # => {'name' => ['bob, 'robert']}
    def previous_changes
      @previously_changed
    end

    # Map of change <tt>attr => original value</tt>.
    def changed_attributes
      @changed_attributes ||= {}
    end

    private

      # Handle <tt>*_changed?</tt> for +method_missing+.
      def attribute_changed?(attr)
        changed_attributes.include?(attr)
      end

      # Handle <tt>*_change</tt> for +method_missing+.
      def attribute_change(attr)
        [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
      end

      # Handle <tt>*_was</tt> for +method_missing+.
      def attribute_was(attr)
        attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
      end

      # Handle <tt>*_will_change!</tt> for +method_missing+.
      def attribute_will_change!(attr)
        begin
          value = __send__(attr)
          value = value.duplicable? ? value.clone : value
        rescue TypeError, NoMethodError
        end

        changed_attributes[attr] = value unless changed_attributes.include?(attr)
      end

      # Handle <tt>reset_*!</tt> for +method_missing+.
      def reset_attribute!(attr)
        __send__("#{attr}=", changed_attributes[attr]) if attribute_changed?(attr)
      end
  end
end
Something went wrong with that request. Please try again.