Skip to content

Commit

Permalink
Merge pull request #2 from johnnyshields/mongoid3-atomic-persistence
Browse files Browse the repository at this point in the history
Fix atomic persistence for Mongoid3
  • Loading branch information
johnnyshields committed Mar 11, 2016
2 parents 39f7eeb + 54ec3d7 commit b819d24
Show file tree
Hide file tree
Showing 27 changed files with 3,033 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@

#### 0.1.4

* Add atomic persistence support for Mongoid 3 (previously only contextual was supported).

#### 0.1.3

* Support index-related Rake tasks.
Expand Down
226 changes: 225 additions & 1 deletion lib/patches/atomic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,228 @@ def collect_operations(ops)
end
end
end
end

module Mongoid
module Persistence
module Atomic

# push_all is deprecated so not supported

def add_to_set_with_mongoid4(*args)
if args.length == 1 && args.first.is_a?(Hash)
adds = args.first
prepare_atomic_operation do |ops|
process_atomic_operations(adds) do |field, value|
existing = send(field) || (attributes[field] ||= [])
values = [ value ].flatten(1)
values.each do |val|
existing.push(val) unless existing.include?(val)
end
ops[atomic_attribute_name(field)] = { "$each" => values }
end
{ "$addToSet" => ops }
end
else
add_to_set_without_mongoid4(*args)
end
end
alias_method_chain :add_to_set, :mongoid4

def bit_with_mongoid4(*args)
if args.length == 1 && args.first.is_a?(Hash)
operations = args.first
prepare_atomic_operation do |ops|
process_atomic_operations(operations) do |field, values|
value = attributes[field]
values.each do |op, val|
value = value & val if op.to_s == "and"
value = value | val if op.to_s == "or"
end
attributes[field] = value
ops[atomic_attribute_name(field)] = values
end
{ "$bit" => ops }
end
else
bit_without_mongoid4(*args)
end
end
alias_method_chain :bit, :mongoid4

def inc_with_mongoid4(*args)
if args.length == 1 && args.first.is_a?(Hash)
increments = args.first
prepare_atomic_operation do |ops|
process_atomic_operations(increments) do |field, value|
increment = value.__to_inc__
current = attributes[field]
attributes[field] = (current || 0) + increment
ops[atomic_attribute_name(field)] = increment
end
{ "$inc" => ops }
end
else
inc_without_mongoid4(*args)
end
end
alias_method_chain :inc, :mongoid4

def pop_with_mongoid4(*args)
if args.length == 1 && args.first.is_a?(Hash)
pops = args.first
prepare_atomic_operation do |ops|
process_atomic_operations(pops) do |field, value|
values = send(field)
value > 0 ? values.pop : values.shift
ops[atomic_attribute_name(field)] = value
end
{ "$pop" => ops }
end
else
pop_without_mongoid4(*args)
end
end
alias_method_chain :pop, :mongoid4

def pull_with_mongoid4(*args)
if args.length == 1 && args.first.is_a?(Hash)
pulls = args.first
prepare_atomic_operation do |ops|
process_atomic_operations(pulls) do |field, value|
(send(field) || []).delete(value)
ops[atomic_attribute_name(field)] = value
end
{ "$pull" => ops }
end
else
pull_without_mongoid4(*args)
end
end
alias_method_chain :pull, :mongoid4

def pull_all_with_mongoid4(*args)
if args.length == 1 && args.first.is_a?(Hash)
pulls = args.first
prepare_atomic_operation do |ops|
process_atomic_operations(pulls) do |field, value|
existing = send(field) || []
value.each{ |val| existing.delete(val) }
ops[atomic_attribute_name(field)] = value
end
{ "$pullAll" => ops }
end
else
pull_all_without_mongoid4(*args)
end
end
alias_method_chain :pull_all, :mongoid4

def push_with_mongoid4(*args)
if args.length == 1 && args.first.is_a?(Hash)
pushes = args.first
prepare_atomic_operation do |ops|
process_atomic_operations(pushes) do |field, value|
existing = send(field) || (attributes[field] ||= [])
values = [ value ].flatten(1)
values.each{ |val| existing.push(val) }
ops[atomic_attribute_name(field)] = { "$each" => values }
end
{ "$push" => ops }
end
else
push_without_mongoid4(*args)
end
end
alias_method_chain :push, :mongoid4

def rename_with_mongoid4(*args)
if args.length == 1 && args.first.is_a?(Hash)
renames = args.first
prepare_atomic_operation do |ops|
process_atomic_operations(renames) do |old_field, new_field|
new_name = new_field.to_s
attributes[new_name] = attributes.delete(old_field)
ops[atomic_attribute_name(old_field)] = atomic_attribute_name(new_name)
end
{ "$rename" => ops }
end
else
rename_without_mongoid4(*args)
end
end
alias_method_chain :rename, :mongoid4

def set_with_mongoid4(*args)
if args.length == 1 && args.first.is_a?(Hash)
setters = args.first
prepare_atomic_operation do |ops|
process_atomic_operations(setters) do |field, value|
process_attribute(field.to_s, value)
ops[atomic_attribute_name(field)] = attributes[field]
end
{ "$set" => ops }
end
else
set_without_mongoid4(*args)
end
end
alias_method_chain :set, :mongoid4

# unset params are consistent, however it returns self in Mongoid 4
# def unset_with_mongoid4(*args)
# unset_without_mongoid4(*args)
# self
# end
# alias_method_chain :unset, :mongoid4

private

def executing_atomically?
!@atomic_updates_to_execute.nil?
end

def post_process_persist(result, options = {})
post_persist unless result == false
errors.clear unless performing_validations?(options)
true
end

def prepare_atomic_operation
operations = yield({})
persist_or_delay_atomic_operation(operations)
self
end

def process_atomic_operations(operations)
operations.each do |field, value|
unless attribute_writable?(field)
raise Errors::ReadonlyAttribute.new(field, value)
end
normalized = database_field_name(field)
yield(normalized, value)
remove_change(normalized)
end
end

def persist_or_delay_atomic_operation(operation)
if executing_atomically?
operation.each do |(name, hash)|
@atomic_updates_to_execute[name] ||= {}
@atomic_updates_to_execute[name].merge!(hash)
end
else
persist_atomic_operations(operation)
end
end

def persist_atomic_operations(operations)
if persisted?
selector = atomic_selector
_root.collection.find(selector).update(operations)
end
end
end
end
end

end
2 changes: 1 addition & 1 deletion lib/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module MongoidMonkey
VERSION = '0.1.3'
VERSION = '0.1.4'
end
17 changes: 17 additions & 0 deletions spec/app/models/address.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@

class Address
include Mongoid::Document
field :address_type
field :number, type: Integer
field :no, type: Integer
field :h, as: :house, type: Integer
field :street
field :city
field :state
field :post_code
field :parent_title
field :services, type: Array
field :aliases, as: :a, type: Array
field :test, type: Array
field :latlng, type: Array
field :map, type: Hash
field :move_in, type: DateTime
field :end_date, type: Date
field :s, type: String, as: :suite
field :name, localize: true

embedded_in :addressable, polymorphic: true
end
14 changes: 14 additions & 0 deletions spec/app/models/name.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class Name
include Mongoid::Document

field :first_name, type: String
field :last_name, type: String
field :parent_title, type: String
field :middle, type: String

embedded_in :namable, polymorphic: true

def set_parent=(set = false)
self.parent_title = namable.title if set
end
end
39 changes: 39 additions & 0 deletions spec/app/models/person.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,43 @@ class Person
index name: 1
index title: 1
index({ ssn: 1 }, { unique: true })
field :username, default: -> { "arthurnn#{rand(0..10)}" }
field :title
field :terms, type: Boolean
field :pets, type: Boolean, default: false
field :age, type: Integer, default: "100"
field :dob, type: Date
field :employer_id
field :lunch_time, type: Time
field :aliases, type: Array
field :map, type: Hash
field :map_with_default, type: Hash, default: {}
field :score, type: Integer
field :blood_alcohol_content, type: Float, default: ->{ 0.0 }
field :last_drink_taken_at, type: Date, default: ->{ 1.day.ago.in_time_zone("Alaska") }
field :ssn
field :owner_id, type: Integer
field :security_code
field :reading, type: Object
field :pattern, type: Regexp
field :override_me, type: Integer
field :at, as: :aliased_timestamp, type: Time
field :t, as: :test, type: String
field :i, as: :inte, type: Integer
field :a, as: :array, type: Array
field :desc, localize: true
field :test_array, type: Array
field :overridden_setter, type: String
field :arrays, type: Array
field :range, type: Range

embeds_many :addresses, as: :addressable, validate: false
embeds_one :name, as: :namable, validate: false do
def extension
"Testing"
end
def dawkins?
first_name == "Richard" && last_name == "Dawkins"
end
end
end
File renamed without changes.
Loading

0 comments on commit b819d24

Please sign in to comment.