Permalink
Browse files

Roll touch back into 2.x series.

[ close #2709 ]
  • Loading branch information...
1 parent 29faf36 commit ac63ae47853b6c6d8b72fa4702014a2ec56d4699 @durran durran committed Jan 21, 2013
View
@@ -281,5 +281,29 @@ def generate_atomic_updates(mods, doc)
mods.add_to_set(doc.atomic_array_add_to_sets)
mods.pull_all(doc.atomic_array_pulls)
end
+
+
+ # Get the atomic updates for a touch operation. Should only include the
+ # updated_at field and the optional extra field.
+ #
+ # @api private
+ #
+ # @example Get the touch atomic updates.
+ # document.touch_atomic_updates
+ #
+ # @param [ Symbol ] field The optional field.
+ #
+ # @return [ Hash ] The atomic updates.
+ #
+ # @since 2.6.0
+ def touch_atomic_updates(field = nil)
+ updates = atomic_updates
+ return {} unless atomic_updates.has_key?("$set")
+ touches = {}
+ updates["$set"].each_pair do |key, value|
+ touches.merge!({ key => value }) if key =~ /updated_at|#{field}/
+ end
+ { "$set" => touches }
+ end
end
end
View
@@ -79,6 +79,39 @@ def save!(options = {})
return true
end
+
+ # Touch the document, in effect updating its updated_at timestamp and
+ # optionally the provided field to the current time. If any belongs_to
+ # relations exist with a touch option, they will be updated as well.
+ #
+ # @example Update the updated_at timestamp.
+ # document.touch
+ #
+ # @example Update the updated_at and provided timestamps.
+ # document.touch(:audited)
+ #
+ # @note This will not autobuild relations if those options are set.
+ #
+ # @param [ Symbol ] field The name of an additional field to update.
+ #
+ # @return [ true/false ] false if record is new_record otherwise true.
+ #
+ # @since 2.6.0
+ def touch(field = nil)
+ return false if _root.new_record?
+ current = Time.now
+ write_attribute(:updated_at, current) if fields["updated_at"]
+ write_attribute(field, current) if field
+
+ touches = touch_atomic_updates(field)
+ unless touches.empty?
+ _root.collection.update(atomic_selector, touches)
+ end
+
+ touchables.each { |name| send(name).try(:touch) }
+ move_changes and true
+ end
+
# Update the document in the datbase.
#
# @example Update an existing document.
View
@@ -24,6 +24,7 @@
require "mongoid/relations/referenced/one"
require "mongoid/relations/reflections"
require "mongoid/relations/synchronization"
+require "mongoid/relations/touchable"
require "mongoid/relations/metadata"
require "mongoid/relations/macros"
@@ -43,6 +44,7 @@ module Relations
include Polymorphic
include Reflections
include Synchronization
+ include Touchable
attr_accessor :metadata
@@ -134,6 +134,7 @@ def belongs_to(name, options = {}, &block)
reference(meta)
autosave(meta)
validates_relation(meta)
+ touchable(meta)
end
end
alias :belongs_to_related :belongs_to
@@ -754,6 +754,18 @@ def order?
!!order
end
+ # Is this relation touchable?
+ #
+ # @example Is the relation touchable?
+ # metadata.touchable?
+ #
+ # @return [ true, false ] If the relation can be touched.
+ #
+ # @since 3.0.0
+ def touchable?
+ !!self[:touch]
+ end
+
private
# Returns the class name for the relation.
@@ -240,7 +240,7 @@ def stores_foreign_key?
#
# @since 2.1.0
def valid_options
- [ :autosave, :foreign_key, :index, :polymorphic ]
+ [ :autosave, :foreign_key, :index, :polymorphic, :touch ]
end
# Get the default validation setting for the relation. Determines if
@@ -0,0 +1,32 @@
+# encoding: utf-8
+module Mongoid
+ module Relations
+ module Touchable
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :touchables
+ self.touchables = []
+ end
+
+ module ClassMethods
+
+ # Add the metadata to the touchable relations if the touch option was
+ # provided.
+ #
+ # @example Add the touchable.
+ # Model.touchable(meta)
+ #
+ # @param [ Metadata ] metadata The relation metadata.
+ #
+ # @return [ Class ] The model class.
+ #
+ # @since 3.0.0
+ def touchable(metadata)
+ self.touchables.push(metadata.name) if metadata.touchable?
+ self
+ end
+ end
+ end
+ end
+end
@@ -2,6 +2,7 @@ class Acolyte
include Mongoid::Document
field :status
field :name
+ field :some_time, type: Time
embeds_many :versions, :as => :memorable
belongs_to :church
@@ -0,0 +1,5 @@
+class Agency
+ include Mongoid::Document
+ include Mongoid::Timestamps::Updated
+ has_many :agents, validate: false
+end
View
@@ -5,6 +5,7 @@ class Agent
field :number
embeds_many :names, :as => :namable
belongs_to :game
+ belongs_to :agency, touch: true
has_and_belongs_to_many :accounts
end
View
@@ -1,5 +1,6 @@
class Label
include Mongoid::Document
+ include Mongoid::Timestamps
field :name, :type => String
field :after_create_called, :type => Boolean, :default => false
@@ -638,6 +638,213 @@
end
end
+ describe "#touch" do
+
+ context "when the document is embedded" do
+
+ let(:band) do
+ Band.create(name: "Placebo")
+ end
+
+ let(:label) do
+ band.create_label(name: "Mute", updated_at: 10.days.ago)
+ end
+
+ before do
+ label.touch
+ end
+
+ it "updates the updated_at timestamp" do
+ label.updated_at.should be_within(1).of(Time.now)
+ end
+
+ it "persists the changes" do
+ label.reload.updated_at.should be_within(1).of(Time.now)
+ end
+ end
+
+ context "when no relations have touch options" do
+
+ context "when no updated at is defined" do
+
+ let(:person) do
+ Acolyte.create
+ end
+
+ context "when no attribute is provided" do
+
+ let!(:touched) do
+ person.touch
+ end
+
+ it "returns true" do
+ touched.should be_true
+ end
+
+ it "does not set the updated at field" do
+ person[:updated_at].should be_nil
+ end
+ end
+
+ context "when an attribute is provided" do
+
+ let!(:touched) do
+ person.touch(:some_time)
+ end
+
+ it "sets the attribute to the current time" do
+ person.some_time.should be_within(5).of(Time.now)
+ end
+
+ it "persists the change" do
+ person.reload.some_time.should be_within(5).of(Time.now)
+ end
+
+ it "returns true" do
+ touched.should be_true
+ end
+ end
+ end
+
+ context "when an updated at is defined" do
+
+ let!(:agent) do
+ Agent.create(updated_at: 2.days.ago)
+ end
+
+ context "when no attribute is provided" do
+
+ let!(:touched) do
+ agent.touch
+ end
+
+ it "sets the updated at to the current time" do
+ agent.updated_at.should be_within(5).of(Time.now)
+ end
+
+ it "persists the change" do
+ agent.reload.updated_at.should be_within(5).of(Time.now)
+ end
+
+ it "returns true" do
+ touched.should be_true
+ end
+
+ it "clears the dirty tracking" do
+ agent.changes.should be_empty
+ end
+ end
+
+ context "when an attribute is provided" do
+
+ let!(:touched) do
+ agent.touch(:dob)
+ end
+
+ it "sets the updated at to the current time" do
+ agent.updated_at.should be_within(5).of(Time.now)
+ end
+
+ it "sets the attribute to the current time" do
+ agent.dob.should be_within(5).of(Time.now)
+ end
+
+ it "sets both attributes to the exact same time" do
+ agent.updated_at.should be_within(1).of(agent.dob)
+ end
+
+ it "persists the updated at change" do
+ agent.reload.updated_at.should be_within(5).of(Time.now)
+ end
+
+ it "persists the attribute change" do
+ agent.reload.dob.should be_within(5).of(Time.now)
+ end
+
+ it "returns true" do
+ touched.should be_true
+ end
+
+ it "clears the dirty tracking" do
+ agent.changes.should be_empty
+ end
+ end
+ end
+
+ context "when record is new" do
+
+ let(:agent) do
+ Agent.new(updated_at: 2.days.ago)
+ end
+
+ context "when no attribute is provided" do
+
+ let!(:touched) do
+ agent.touch
+ end
+
+ it "returns false" do
+ touched.should be_false
+ end
+ end
+
+ context "when an attribute is provided" do
+
+ let!(:touched) do
+ agent.touch(:dob)
+ end
+
+ it "returns false" do
+ touched.should be_false
+ end
+ end
+ end
+ end
+
+ context "when relations have touch options" do
+
+ let!(:agent) do
+ Agent.create
+ end
+
+ context "when the relation is nil" do
+
+ context "when the relation autobuilds" do
+
+ let!(:touched) do
+ agent.touch
+ end
+
+ it "does nothing to the relation" do
+ agent.instance_variable_get(:@agency).should be_nil
+ end
+ end
+ end
+
+ context "when the relation is not nil" do
+
+ let!(:agency) do
+ Agency.create.tap do |a|
+ a.agents << agent
+ a.unset(:updated_at)
+ end
+ end
+
+ let!(:touched) do
+ agent.touch
+ end
+
+ it "sets the parent updated at to the current time" do
+ agency.updated_at.should be_within(5).of(Time.now)
+ end
+
+ it "persists the change" do
+ agency.reload.updated_at.should be_within(5).of(Time.now)
+ end
+ end
+ end
+ end
+
describe "#update_attribute" do
let(:post) do
Oops, something went wrong.

0 comments on commit ac63ae4

Please sign in to comment.