Permalink
Browse files

Roll touch back into 2.x series.

[ close #2709 ]
  • Loading branch information...
durran committed Jan 21, 2013
1 parent 29faf36 commit ac63ae47853b6c6d8b72fa4702014a2ec56d4699
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.