module Ardm
Approach
We need to detect whether or not the underlying Hash or Array changed and update the dirty-ness of the encapsulating Resource accordingly (so that it will actually save).
DM’s state-tracking code only triggers dirty-ness by comparing the new value against the instance’s Property’s current value. WRT mutation, we have to choose one of the following approaches:
(1) mutate a copy ("after"), then invoke the Resource assignment and State tracking (2) create a copy ("before"), mutate self ("after"), then invoke the Resource assignment and State tracking
(1) seemed simpler at first, but it required additional steps to alias the original (pre-hooked) methods before overriding them (so they could be invoked externally, ala self.clone.send(“orig_…”)), and more importantly it resulted in any external references keeping their old value (instead of getting the new), like so:
copy = instance.json copy[:some] = :value instance.json[:some] == :value => true copy[:some] == :value => false # fk!
In order to do (2) and still have State tracking trigger normally, we need to ensure the Property
has a different value other than self when the State tracking does the comparison. This equates to setting the Property
directly to the “before” value (a clone and thus a different object/value) before invoking the Resource Property/attribute assignment.
The cloning of any value might sound expensive, but it’s identical in cost to what you already had to do: assign a cloned copy in order to trigger dirty-ness (e.g. ::Ardm::Property::Json
):
model.json = model.json.merge({:some=>:value})
Hooking Core Classes
We want to hook certain methods on Hash and Array to trigger dirty-ness in the resource. However, because these are core classes, they are individually mapped to C primitives and thus cannot be hooked through send/#__send__. We have to override each method, but we don’t want to write a lot of code.
Minimally Invasive
We also want to extend behaviour of existing class instances instead of impersonating/delegating from a proxy class of our own, or overriding a global class behaviour. This is the most flexible approach and least prone to error, since it leaves open the option for consumers to proxy or override global classes, and is less likely to interfere with method_missing/etc shenanigans.
Nested Object
Mutations
Since we use {Array,Hash}#hash to compare before & after, and hash accounts for/traverses nested structures, no “deep” inspection logic is technically necessary. However, Resource#dirty? only queries a cache of dirtied attributes, whose own population strategy is to hook assignment (instead of interrogating properties on demand). So the approach is still limited to top-level mutators.
Maybe consider optional “advisory” Property#dirty? method for Resource#dirty? that custom properties could use for this purpose.
TODO: add support for detecting mutations in nested objects, but we can’t
catch the assignment from here (yet?).
TODO: ensure we covered all indirectly-mutable classes that DM uses underneath
a property type
TODO: figure out how to hook core class methods on RBX (which do use send)
Public Ardm
Logger
API¶ ↑
To replace an existing logger with a new one:
Ardm::Logger.set_log(log{String, IO},level{Symbol, String})
Available logging levels are
Ardm::Logger::{ Fatal, Error, Warn, Info, Debug }
Logging via:
Ardm.logger.fatal(message<String>,&block) Ardm.logger.error(message<String>,&block) Ardm.logger.warn(message<String>,&block) Ardm.logger.info(message<String>,&block) Ardm.logger.debug(message<String>,&block)
Logging with autoflush:
Ardm.logger.fatal!(message<String>,&block) Ardm.logger.error!(message<String>,&block) Ardm.logger.warn!(message<String>,&block) Ardm.logger.info!(message<String>,&block) Ardm.logger.debug!(message<String>,&block)
Flush the buffer to
Ardm.logger.flush
Remove the current log object
Ardm.logger.close
Private Ardm
Logger
API¶ ↑
To initialize the logger you create a new object, proxies to set_log.
Ardm::Logger.new(log{String, IO},level{Symbol, String})
Constants
- Collection
- NotImplemented
- Property
- Record
- RecordNotFound
- SaveFailureError
- VERSION
- Validations
Attributes
Public Class Methods
Yield if Ardm
has loaded ActiveRecord ORM.
@api public
# File lib/ardm.rb, line 93 def ar yield if block_given? && ar? end
Return true if Ardm
has loaded ActiveRecord ORM.
@api public
# File lib/ardm.rb, line 75 def ar? orm == :ar end
# File lib/ardm/ar.rb, line 40 def self.define_datamapper_constant! require 'ardm/ar/data_mapper_constant' end
Yield if Ardm
has loaded DataMapper ORM.
@api public
# File lib/ardm.rb, line 102 def dm yield if block_given? && dm? end
Return true if Ardm
has loaded DataMapper ORM.
@api public
# File lib/ardm.rb, line 84 def dm? orm == :dm end
# File lib/ardm.rb, line 68 def lib "ardm/#{orm}" end
Check which ORM is loaded in Ardm
.
@api public
# File lib/ardm.rb, line 36 def orm if @orm return @orm else self.orm = ENV['ORM'] end @orm end
Set which orm to load.
@api public
# File lib/ardm.rb, line 48 def orm=(orm) neworm = case orm.to_s when /(ar|active_?record)/ then :ar when /(dm|data_?mapper)/ then :dm when "" then raise "Specify Ardm.orm by assigning :ar or :dm or by setting ENV['ORM']" else raise "Unknown Ardm.orm. Expected: (ar|dm). Got: #{orm.inspect}" end if @orm == neworm return @orm end if defined?(Ardm::Ar) || defined?(Ardm::Dm) raise "Cannot change Ardm.orm when #{self.orm} libs are already loaded." end @orm = neworm end
# File lib/ardm.rb, line 109 def rails3? ar? && ::ActiveRecord::VERSION::STRING >= "3.0" && ::ActiveRecord::VERSION::STRING <= "4.0" end
# File lib/ardm.rb, line 113 def rails4? ar? && !rails3? end
Setup the ORM using orm arg or $ORM, then require the correct shim libs.
If an ORM is not specified as an argument, ENV will be used. If $ORM is not set, then Ardm
will raise.
Execute the block if one is given. This is a good time to require active_record
or dm-core using Ardm.ar
or Ardm.dm
blocks.
Ardm.setup ENV['ORM'] do Ardm.ar do Bundler.require(:active_record) require "active_record/railtie" end Ardm.dm { Bundler.require(:data_mapper) } end
The Ardm
shim libs will be required after the block returns.
@api public
# File lib/ardm.rb, line 27 def setup(orm=nil) self.orm = orm if orm yield self if block_given? require lib end