lib/kentouzu/has_drafts.rb in kentouzu-0.1.2 vs lib/kentouzu/has_drafts.rb in kentouzu-0.2.0
- old
+ new
@@ -3,64 +3,82 @@
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
+ # By calling this in your model all subsequent calls to save will instead create a draft.
+ # Drafts are available through the `drafts` association.
+ #
+ # Options:
+ # :class_name The name of a custom Draft class. Should inherit from `Kentouzu::Draft`.
+ # Default is `'Draft'`.
+ # :draft The name for the method which returns the draft the instance was reified from.
+ # Default is `:draft`.
+ # :drafts The name to use for the drafts association.
+ # Default is `:drafts`.
+ # :if Proc that allows you to specify the conditions under which drafts are made.
+ # :ignore An Array of attributes that will be ignored when creating a `Draft`.
+ # Can also accept a Has as an argument where each key is the attribute to ignore (either
+ # a `String` or `Symbol`) and each value is a `Proc` whose return value, `true` or
+ # `false`, determines if it is ignored.
+ # :meta A hash of extra data to store. Each key in the hash (either a `String` or `Symbol`)
+ # must be a column on the `drafts` table, otherwise it is ignored. You must add these
+ # columns yourself. The values are either objects or procs (which are called with `self`,
+ # i.e. the model the draft is being made from).
+ # :on An array of events that will cause a draft to be created.
+ # Defaults to `[:create, :update, :destroy]`.
+ # :only Inverse of the `:ignore` option. Only the attributes supplied will be passed along to
+ # the draft.
+ # :unless Proc that allows you to specify the conditions under which drafts are not made.
def has_drafts(options = {})
+ # Only include the instance methods when this `has_drafts` is called to avoid cluttering up models.
send :include, InstanceMethods
+ # Add `before_draft_save`, `after_draft_save`, and `around_draft_save` callbacks.
send :define_model_callbacks, :draft_save
class_attribute :draft_association_name
self.draft_association_name = options[:draft] || :draft
+ # The draft this instance was reified from.
attr_accessor self.draft_association_name
class_attribute :draft_class_name
self.draft_class_name = options[:class_name] || 'Draft'
- class_attribute :ignore
- self.ignore = ([options[:ignore]].flatten.compact || []).map &:to_s
+ class_attribute :draft_options
+ self.draft_options = options.dup
- class_attribute :if_condition
- self.if_condition = options[:if]
+ [:ignore, :only].each do |option|
+ draft_options[option] = [draft_options[option]].flatten.compact.map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
+ end
- class_attribute :unless_condition
- self.unless_condition = options[:unless]
+ draft_options[:meta] ||= {}
- class_attribute :skip
- self.skip = ([options[:skip]].flatten.compact || []).map &:to_s
-
- class_attribute :only
- self.only = ([options[:only]].flatten.compact || []).map &:to_s
-
- class_attribute :drafts_enabled_for_model
- self.drafts_enabled_for_model = true
-
class_attribute :drafts_association_name
self.drafts_association_name = options[:drafts] || :drafts
- if ActiveRecord::VERSION::STRING.to_f >= 4.0 # `has_many` syntax for specifying order uses a lambda in Rails 4
+ if ActiveRecord::VERSION::MAJOR >= 4 # `has_many` syntax for specifying order uses a lambda in Rails 4
has_many self.drafts_association_name,
lambda { order("#{Kentouzu.timestamp_field} ASC, #{self.primary_key} ASC") },
:class_name => draft_class_name,
:as => :item,
:dependent => :destroy
else
has_many self.drafts_association_name,
:class_name => draft_class_name,
:as => :item,
- :order => "#{Kentouzu.timestamp_field} ASC, #{self.draft_class_name.constantize.primary_key} ASC",
+ :order => "#{Kentouzu.timestamp_field} ASC, #{self.draft_class.primary_key} ASC",
:dependent => :destroy
end
define_singleton_method "new_#{drafts_association_name.to_s}".to_sym do
Draft.where(:item_type => self.name, :event => 'create')
end
define_singleton_method "all_with_reified_#{drafts_association_name.to_s}".to_sym do |order_by = Kentouzu.timestamp_field, &block|
- existing_drafts = Draft.where("`drafts`.`item_type` = \"#{self.base_class.name}\" AND `drafts`.`item_id` IS NOT NULL").group_by { |draft| draft.item_id }.map { |k, v| v.sort_by { |draft| draft.created_at }.last }
+ existing_drafts = Draft.where("`drafts`.`item_type` = \"#{self.base_class.name}\" AND `drafts`.`item_id` IS NOT NULL").group_by { |draft| draft.item_id }.map { |_, v| v.sort_by { |draft| draft.created_at }.last }
new_drafts = Draft.where("`drafts`.`item_type` = \"#{self.base_class.name}\" AND `drafts`.`item_id` IS NULL")
existing_reified_objects = existing_drafts.map { |draft| draft.reify }
@@ -81,44 +99,61 @@
end
all_objects
end
+ def drafts_off!
+ Kentouzu.enabled_for_model(self, false)
+ end
+
def drafts_off
- self.drafts_enabled_for_model = false
+ warn 'DEPRECATED: use `drafts_off!` instead of `drafts_off`. Will be removed in Kentouzu 0.3.0.'
+
+ self.drafts_off!
end
+ def drafts_on!
+ Kentouzu.enabled_for_model(self, true)
+ end
+
def drafts_on
- self.drafts_enabled_for_model = true
+ warn 'DEPRECATED: use `drafts_on!` instead of `drafts_on`. Will be removed in Kentouzu 0.3.0.'
+
+ self.drafts_on!
end
+
+ def drafts_enabled_for_model?
+ Kentouzu.enabled_for_model?(self)
+ end
+
+ def draft_class
+ @draft_class ||= draft_class_name.constantize
+ end
end
end
module InstanceMethods
+ # Override the default `save` method and replace it with one that checks to see if a draft should be saved.
+ # If a draft should be saved the original object instance is left untouched and a new draft is created.
def self.included(base)
default_save = base.instance_method(:save)
base.send :define_method, :save do
if switched_on? && save_draft?
- data = {
- :item_type => self.class.base_class.to_s,
- :item_id => self.id,
- :event => self.persisted? ? 'update' : 'create',
- :source_type => Kentouzu.source.present? ? Kentouzu.source.class.to_s : nil,
- :source_id => Kentouzu.source.present? ? Kentouzu.source.id : nil,
- :object => self.to_yaml
- }
+ save_draft
+ else
+ default_save.bind(self).call
+ end
+ end
- data.merge!(Kentouzu.controller_info.slice(:item_type, :item_id, :event, :source_type, :source_id) || {})
+ default_save_with_bang = base.instance_method(:save!)
- draft = Draft.new(data)
-
- run_callbacks :draft_save do
- draft.save
- end
+ base.send :define_method, :save! do
+ if switched_on? && save_draft?
+ save_draft
else
- default_save.bind(self).call
+ default_save_with_bang.bind(self).call
end
end
end
def live?
@@ -149,25 +184,78 @@
method ? method.to_proc.call(self) : yield
ensure
self.class.drafts_on if drafts_were_enabled
end
+ def drafts_enabled_for_model?
+ self.class.drafts_enabled_for_model?
+ end
+
private
def draft_class
draft_class_name.constantize
end
def source_draft
send self.class.draft_association_name
end
+ def merge_metadata(data)
+ draft_options[:meta].each do |key, value|
+ if value.respond_to?(:call)
+ data[key] = value.call(self)
+ elsif value.is_a?(Symbol) && respond_to?(value)
+ data[key] = send(value)
+ else
+ data[key] = value
+ end
+ end
+
+ (Kentouzu.controller_info || {}).each do |key, value|
+ if value.respond_to?(:call)
+ data[key] = value.call(self)
+ elsif value.is_a?(Symbol) && respond_to?(value)
+ data[key] = send(value)
+ end
+ end
+
+ data
+ end
+
+ def save_draft
+ data = {
+ :item_type => self.class.base_class.to_s,
+ :item_id => self.id,
+ :event => draft_event.to_s,
+ :source_type => Kentouzu.source.present? ? Kentouzu.source.class.to_s : nil,
+ :source_id => Kentouzu.source.present? ? Kentouzu.source.id : nil,
+ :object => self.as_json(include: self.class.reflect_on_all_associations(:has_many).map { |a| a.name }.reject { |a| a == :drafts }).to_yaml
+ }
+
+ draft = Draft.new(merge_metadata(data))
+
+ run_callbacks :draft_save do
+ draft.save
+ end
+ end
+
+ def draft_event
+ @draft_event ||= self.persisted? ? :update : :create
+ end
+
def switched_on?
- Kentouzu.enabled? && Kentouzu.enabled_for_controller? && self.class.drafts_enabled_for_model
+ Kentouzu.enabled? && Kentouzu.enabled_for_controller? && self.drafts_enabled_for_model?
end
def save_draft?
- (if_condition.blank? || if_condition.call(self)) && !unless_condition.try(:call, self)
+ on_events = Array(self.draft_options[:on])
+
+ if_condition = self.draft_options[:if]
+
+ unless_condition = self.draft_options[:unless]
+
+ (on_events.empty? || on_events.include?(draft_event)) && (if_condition.blank? || if_condition.call(self)) && !unless_condition.try(:call, self)
end
end
end
end