Skip to content

Commit

Permalink
Closes #31 Allow column name to be configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Peters committed Nov 13, 2016
1 parent 47042d1 commit 340e632
Show file tree
Hide file tree
Showing 11 changed files with 588 additions and 665 deletions.
19 changes: 14 additions & 5 deletions lib/draftsman.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,19 @@ def self.whodunnit
draftsman_store[:whodunnit]
end

# Sets who is responsible for any changes that occur.
# You would normally use this in a migration or on the console,
# when working with models directly. In a controller, it is set
# automatically to the `current_user`.
# Returns the field which records whodunnit data.
def self.whodunnit_field
Draftsman.config.whodunnit_field
end

# Sets global attribute name for `whodunnit` data.
def self.whodunnit_field=(field_name)
Draftsman.config.whodunnit_field = field_name
end

# Sets who is responsible for any changes that occur. You would normally use
# this in a migration or on the console when working with models directly. In
# a controller, it is set automatically to the `current_user`.
def self.whodunnit=(value)
draftsman_store[:whodunnit] = value
end
Expand All @@ -95,7 +104,7 @@ def self.whodunnit=(value)

# Thread-safe hash to hold Draftman's data. Initializing with needed default values.
def self.draftsman_store
Thread.current[:draft] ||= { :draft_class_name => 'Draftsman::Draft' }
Thread.current[:draft] ||= { draft_class_name: 'Draftsman::Draft' }
end

# Returns Draftman's configuration object.
Expand Down
3 changes: 2 additions & 1 deletion lib/draftsman/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
module Draftsman
class Config
include Singleton
attr_accessor :serializer, :timestamp_field
attr_accessor :serializer, :timestamp_field, :whodunnit_field

def initialize
@timestamp_field = :created_at
@mutex = Mutex.new
@serializer = Draftsman::Serializers::Yaml
@enabled = true
@whodunnit_field = :whodunnit
end

# Indicates whether Draftsman is on or off. Default: true.
Expand Down
24 changes: 10 additions & 14 deletions lib/draftsman/draft.rb
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
class Draftsman::Draft < ActiveRecord::Base
# Mass assignment (for <= ActiveRecord 3.x)
if Draftsman.active_record_protected_attributes?
attr_accessible :item_type, :item_id, :item, :event, :whodunnit, :object, :object_changes, :previous_draft
end

# Associations
belongs_to :item, :polymorphic => true
belongs_to :item, polymorphic: true

# Validations
validates_presence_of :event

def self.with_item_keys(item_type, item_id)
scoped :conditions => { :item_type => item_type, :item_id => item_id }
scoped conditions: { item_type: item_type, item_id: item_id }
end

def self.creates
where :event => 'create'
where(event: :create)
end

def self.destroys
where :event => 'destroy'
where(event: :destroy)
end

# Returns whether the `object` column is using the `json` type supported by PostgreSQL.
# Returns whether the `object` column is using the `json` type supported by
# PostgreSQL.
def self.object_col_is_json?
@object_col_is_json ||= columns_hash['object'].type == :json
end

# Returns whether the `object_changes` column is using the `json` type supported by PostgreSQL.
# Returns whether the `object_changes` column is using the `json` type
# supported by PostgreSQL.
def self.object_changes_col_is_json?
@object_changes_col_is_json ||= columns_hash['object_changes'].type == :json
end
Expand All @@ -38,14 +35,13 @@ def self.previous_draft_col_is_json?
end

def self.updates
where :event => 'update'
where(event: :update)
end

# Returns what changed in this draft. Similar to `ActiveModel::Dirty#changes`.
# Returns `nil` if your `drafts` table does not have an `object_changes` text column.
def changeset
return nil unless self.class.column_names.include? 'object_changes'

return nil unless self.class.column_names.include?('object_changes')
@changeset ||= load_changeset
end

Expand Down
73 changes: 43 additions & 30 deletions lib/draftsman/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,50 @@ def self.included(base)
end

module ClassMethods
# Declare this in your model to enable the Draftsman API for it. A draft of the model is available in the `draft`
# association (if one exists).
# Declare this in your model to enable the Draftsman API for it. A draft
# of the model is available in the `draft` association (if one exists).
#
# Options:
#
# :class_name
# The name of a custom `Draft` class. This class should inherit from `Draftsman::Draft`. A global default can be
# set for this using `Draftsman.draft_class_name=` if the default of `Draftsman::Draft` needs to be overridden.
# The name of a custom `Draft` class. This class should inherit from
# `Draftsman::Draft`. A global default can be set for this using
# `Draftsman.draft_class_name=` if the default of `Draftsman::Draft` needs
# to be overridden.
#
# :ignore
# An array of attributes for which an update to a `Draft` will not be stored if they are the only ones changed.
# An array of attributes for which an update to a `Draft` will not be
# stored if they are the only ones changed.
#
# :only
# Inverse of `ignore` - a new `Draft` will be created only for these attributes if supplied. It's recommended that
# you only specify optional attributes for this (that can be empty).
# Inverse of `ignore` - a new `Draft` will be created only for these
# attributes if supplied. It's recommended that you only specify optional
# attributes for this (that can be empty).
#
# :skip
# Fields to ignore completely. As with `ignore`, updates to these fields will not create a new `Draft`. In
# addition, these fields will not be included in the serialized versions of the object whenever a new `Draft` is
# created.
# Fields to ignore completely. As with `ignore`, updates to these fields
# will not create a new `Draft`. In addition, these fields will not be
# included in the serialized versions of the object whenever a new `Draft`
# is created.
#
# :meta
# A hash of extra data to store. You must add a column to the `drafts` table for each key. Values are objects or
# `procs` (which are called with `self`, i.e. the model with the `has_drafts`). See
# `Draftsman::Controller.info_for_draftsman` for an example of how to store data from the controller.
# A hash of extra data to store. You must add a column to the `drafts`
# table for each key. Values are objects or `procs` (which are called with
# `self`, i.e. the model with the `has_drafts`). See
# `Draftsman::Controller.info_for_draftsman` for an example of how to
# store data from the controller.
#
# :draft
# The name to use for the `draft` association shortcut method. Default is `:draft`.
# The name to use for the `draft` association shortcut method. Default is
# `:draft`.
#
# :published_at
# The name to use for the method which returns the published timestamp. Default is `published_at`.
# The name to use for the method which returns the published timestamp.
# Default is `published_at`.
#
# :trashed_at
# The name to use for the method which returns the soft delete timestamp. Default is `trashed_at`.
# The name to use for the method which returns the soft delete timestamp.
# Default is `trashed_at`.
def has_drafts(options = {})
# Lazily include the instance methods so we don't clutter up
# any more ActiveRecord models than we need to.
Expand All @@ -50,6 +60,8 @@ def has_drafts(options = {})

# Define before/around/after callbacks on each drafted model
send :extend, ActiveModel::Callbacks
# TODO: Remove `draft_creation`, `draft_update`, and `draft_destroy` in
# v1.0.
define_model_callbacks :save_draft, :draft_creation, :draft_update, :draft_destruction, :draft_destroy

class_attribute :draftsman_options
Expand Down Expand Up @@ -78,7 +90,7 @@ def has_drafts(options = {})
self.trashed_at_attribute_name = options[:trashed_at] || :trashed_at

# `belongs_to :draft` association
belongs_to self.draft_association_name, :class_name => self.draft_class_name, :dependent => :destroy
belongs_to(self.draft_association_name, class_name: self.draft_class_name, dependent: :destroy)

# Scopes
scope :drafted, (lambda do |referenced_table_name = nil|
Expand Down Expand Up @@ -231,11 +243,11 @@ def _draft_creation
return false unless self.save

data = {
item: self,
event: :create,
whodunnit: Draftsman.whodunnit,
object: object_attrs_for_draft_record
item: self,
event: :create,
object: object_attrs_for_draft_record
}
data[Draftsman.whodunnit_field] = Draftsman.whodunnit
data[:object_changes] = changes_for_draftsman(previous_changes: true) if track_object_changes_for_draft?
data = merge_metadata_for_draft(data)

Expand All @@ -261,11 +273,11 @@ def _draft_creation
def _draft_destruction
transaction do
data = {
:item => self,
:event => 'destroy',
:whodunnit => Draftsman.whodunnit,
:object => object_attrs_for_draft_record
item: self,
event: :destroy,
object: object_attrs_for_draft_record
}
data[Draftsman.whodunnit_field] = Draftsman.whodunnit

# Stash previous draft in case it needs to be reverted later
if self.draft?
Expand Down Expand Up @@ -324,14 +336,15 @@ def _draft_update
# If updating a creation draft, also update this item
if self.draft? && send(self.class.draft_association_name).create?
data = {
item: self,
whodunnit: Draftsman.whodunnit,
object: object_attrs_for_draft_record
item: self,
object: object_attrs_for_draft_record
}
data[Draftsman.whodunnit_field] = Draftsman.whodunnit

if track_object_changes_for_draft?
data[:object_changes] = changes_for_draftsman(changed_from: self.send(self.class.draft_association_name).changeset)
end

data = merge_metadata_for_draft(data)
send(self.class.draft_association_name).update_attributes(data)
self.save
Expand Down Expand Up @@ -486,8 +499,8 @@ def track_object_changes_for_draft?

# Sets `trashed_at` attribute to now and saves to the database immediately.
def trash!
write_attribute self.class.trashed_at_attribute_name, Time.now
self.update_column self.class.trashed_at_attribute_name, send(self.class.trashed_at_attribute_name)
write_attribute(self.class.trashed_at_attribute_name, Time.now)
self.update_column(self.class.trashed_at_attribute_name, send(self.class.trashed_at_attribute_name))
end

# Updates skipped attributes' values on this model.
Expand Down
16 changes: 7 additions & 9 deletions spec/draftsman_spec.rb
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
require 'spec_helper.rb'

describe ::Draftsman do
subject { ::Draftsman }

it 'passes our sanity test' do
expect(subject).to be_a Module
expect(::Draftsman).to be_a Module
end

describe :whodunnit do
before(:all) { ::Draftsman.whodunnit = 'foobar' }
describe '.whodunnit' do
before(:all) { ::Draftsman.whodunnit = :foobar }

it 'clears out `whodunnit` before each test' do
expect(subject.whodunnit).to be_nil
expect(::Draftsman.whodunnit).to be_nil
end
end

describe :controller_info do
before(:all) { ::Draftsman.controller_info = { foo: 'bar' } }
describe '.controller_info' do
before(:all) { ::Draftsman.controller_info = { foo: :bar } }

it 'clears out `controller_info` before each test' do
expect(subject.controller_info).to eql Hash.new
expect(::Draftsman.controller_info).to eql Hash.new
end
end
end
95 changes: 0 additions & 95 deletions spec/dummy/db/migrate/20110208155312_set_up_test_tables.rb

This file was deleted.

Loading

0 comments on commit 340e632

Please sign in to comment.