Skip to content

Commit

Permalink
Merge pull request #4 from jdickey/create-the-thing
Browse files Browse the repository at this point in the history
Create the Thing entered in the new-thing form. Widgets and objects and forms; oh my!
  • Loading branch information
jdickey committed Dec 21, 2015
2 parents 5b85209 + 5ccad4b commit c4ce908
Show file tree
Hide file tree
Showing 19 changed files with 814 additions and 65 deletions.
40 changes: 20 additions & 20 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ GEM
ansi (1.5.0)
arel (6.0.3)
arrayfields (4.9.2)
ast (2.1.0)
ast (2.2.0)
astrolabe (1.3.1)
parser (~> 2.2)
awesome_print (1.6.1)
Expand Down Expand Up @@ -84,16 +84,17 @@ GEM
coderay (1.1.0)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
coffee-rails (4.1.0)
coffee-rails (4.1.1)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.0)
railties (>= 4.0.0, < 5.1.x)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.10.0)
concord (0.1.5)
adamantium (~> 0.2.0)
equalizer (~> 0.0.9)
concurrent-ruby (1.0.0)
coveralls (0.8.10)
json (~> 1.8)
rest-client (>= 1.6.8, < 2)
Expand Down Expand Up @@ -157,11 +158,11 @@ GEM
nokogiri (>= 1.5.9)
mail (2.6.3)
mime-types (>= 1.16, < 3)
main (6.1.0)
main (6.2.0)
arrayfields (>= 4.7.4)
chronic (>= 0.6.2)
fattr (>= 2.2.0)
map (>= 5.1.0)
map (>= 6.1.0)
map (6.5.5)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
Expand Down Expand Up @@ -194,7 +195,7 @@ GEM
ruby-progressbar
multi_json (1.11.2)
netrc (0.11.0)
nokogiri (1.6.7)
nokogiri (1.6.7.1)
mini_portile2 (~> 2.0.0.rc2)
ox (2.2.2)
parser (2.2.3.0)
Expand Down Expand Up @@ -242,8 +243,8 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.2)
loofah (~> 2.0)
rails_semantic_logger (1.7.0)
semantic_logger (>= 2.5.0)
rails_semantic_logger (1.8.0)
semantic_logger (~> 2.20)
railties (4.2.5)
actionpack (= 4.2.5)
activesupport (= 4.2.5)
Expand Down Expand Up @@ -276,7 +277,7 @@ GEM
ruby_parser (3.7.2)
sexp_processor (~> 4.1)
rubyzip (1.1.7)
sass (3.4.19)
sass (3.4.20)
sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0)
sass (~> 3.1)
Expand All @@ -288,29 +289,28 @@ GEM
multi_json (~> 1.0)
rubyzip (~> 1.0)
websocket (~> 1.0)
semantic_logger (2.18.0)
sync_attr (~> 2.0)
thread_safe (~> 0.1)
semantic_logger (2.21.0)
concurrent-ruby (~> 1.0)
sequel (4.29.0)
sequel-rails (0.9.11)
actionpack (>= 3.2.0)
activemodel
railties (>= 3.2.0)
sequel (>= 3.28, < 5.0)
sexp_processor (4.6.0)
simplecov (0.11.0)
simplecov (0.11.1)
docile (~> 1.1.0)
json (~> 1.8)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
slop (3.6.0)
sprockets (3.4.1)
sprockets (3.5.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (2.3.3)
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
sync_attr (2.0.0)
sprockets-rails (3.0.0)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
term-ansicolor (1.3.2)
tins (~> 1.0)
thin (1.6.4)
Expand Down Expand Up @@ -401,4 +401,4 @@ DEPENDENCIES
web-console (~> 2.0)

BUNDLED WITH
1.10.6
1.11.2
21 changes: 9 additions & 12 deletions app/controllers/things_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,23 @@

# ThingsController is our one and only demo controller.
class ThingsController < ApplicationController
include SemanticLogger::Loggable
# include SemanticLogger::Loggable

def index
index_responder { |things| @things = things }
self
respond_with(IndexResponder) { |things| @things = things }
end

def new
new_responder { |thing| @thing = thing }
logger.debug 'Returning from #new', @thing
self
respond_with(NewResponder) { |thing| @thing = thing }
end

private

def index_responder
IndexResponder.new(self).call(params) { |things| yield things }
def create
respond_with(CreateResponder) { |thing| @thing = thing }
end

def new_responder
NewResponder.new(self).call(params) { |thing| yield thing }
private

def respond_with(responder, &_block)
responder.new(self).call(params) { |product| yield product }
end
end
60 changes: 60 additions & 0 deletions app/controllers/things_controller/create_responder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

require 'create_thing'

# ThingsController is our one and only demo controller.
class ThingsController < ApplicationController
# Encapsulates all logic involved in create action, successful or otherwise.
class CreateResponder
# include SemanticLogger::Loggable

def initialize(controller)
@controller = controller
self
end

def call(params, &_block)
create_and_save_thing params
yield @thing
handle_result
end

private

attr_reader :controller, :thing

def create_thing(params)
@thing = FormObjects::CreateThing.new params['thing']
self
end

def create_and_save_thing(params)
create_thing params
@thing.save if @thing.valid?
self
end

def handle_result
return handle_success if @thing.valid?
handle_failure
end

def handle_failure
# Controller's @thing should already be set by this point per yield
controller.render 'new'
self
end

def handle_success
controller.redirect_to success_redirect_target, success_flash
end

def success_flash
message = ['Added your new "', '" Thing!'].join thing.name
{ flash: { success: message } }
end

def success_redirect_target
controller.root_path
end
end # class ThingsController::CreateResponder
end # class ThingsController
66 changes: 66 additions & 0 deletions app/form_objects/create_thing.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@

require 'virtus'

require_relative 'create_thing/uniqueness'
require_relative 'create_thing/validation_error'

module FormObjects
# All the logic needed to create a new Thing record based on input parameters.
class CreateThing
include SemanticLogger::Loggable
include Virtus.model
include ActiveModel::Validations

attribute :name, String
attribute :initial_quantity, Integer
attribute :description, String

validates :name, presence: true
validates :initial_quantity, numericality: {
greater_than_or_equal_to: 1,
only_integer: true
}
validates :description, presence: true, allow_blank: false
validate :unique?

def new?
!Thing.find(name: name)
end

def save
validate_before_save
new_record.save
self
end

private

def add_uniquness_failure_error
errors.add :name, 'has already been added. Please enter a new one.'
end

def can_save?
valid? && unique?
end

def new_record
Thing.new attributes
end

# Since we don't keep track of record IDs in this class, we have to wing it
# using attributes.
def unique?
return true if Uniqueness.new(attributes).unique?
add_uniquness_failure_error
false
end

def validate_before_save
fail validation_error unless can_save?
end

def validation_error
ValidationError.new invalid_attributes: attributes, errors_found: errors
end
end # class FormObjects::CreateThing
end
38 changes: 38 additions & 0 deletions app/form_objects/create_thing/uniqueness.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

module FormObjects
# All the logic needed to create a new Thing record based on input parameters.
class CreateThing
# Encapsulates testing whether a set of ORM attributes are "unique". This is
# true if either of the following conditions is true:
# 1. No ORM instance exists whose name field has the same name, OR
# 2. An ORM instance exists with ALL fields matching the attributes, whereby
# it is presumed to be the "same" record. We really should ensure that a
# database constraint is set to force that.
class Uniqueness
def initialize(attributes)
@attributes = attributes
self
end

def unique?
!existing || same_instance?
end

private

attr_reader :attributes

def existing
@existing ||= Thing.find(name: attributes[:name])
end

def existing_attributes
existing.values.except(:id)
end

def same_instance?
attributes == existing_attributes
end
end # class FormObjects::CreateThing::Uniqueness
end # class FormObjects::CreateThing
end
27 changes: 27 additions & 0 deletions app/form_objects/create_thing/validation_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

module FormObjects
# All the logic needed to create a new Thing record based on input parameters.
class CreateThing
# Error to be raised when validation of a CreateThing instance fails.
class ValidationError < RuntimeError
include Virtus.model(strict: true)

def initialize(invalid_attributes:, errors_found:)
@invalid_attributes = invalid_attributes
@errors_found = errors_found
super()
end

def message
['Validation of Thing attributes failed. Invalid attributes:',
"#{invalid_attributes}, errors found: #{errors_found.full_messages}"
].join ' '
end

values do
attribute :invalid_attributes, Array[String]
attribute :errors_found, ActiveModel::Errors
end
end # class FormObjects::CreateThing::ValidationError
end # class FormObjects::CreateThing
end
21 changes: 6 additions & 15 deletions app/views/things/index.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@

require File.expand_path 'app/views/things/shared/flashes', Rails.root

# Class encapsulating all page-specific view code for `things/index`.
class Views::Things::Index < Views::Base
needs :things, flash: {}

include Views::Things::Shared

def content
widget Flashes, flashes: flash
non_detail_widgets
detail_table
end
Expand All @@ -18,22 +23,8 @@ def detail_table
table_widget { detail_lines }
end

def each_flash
flash_as_hash.each { |type, message| yield type, message }
end

def flash_as_hash
flash.to_h.symbolize_keys
end

def flash_content
each_flash do |type, message|
widget FlashAlert, type: type, message: message
end
end

def non_detail_widgets
messages = [:title_content, :flash_content, :page_header, :add_thing_button]
messages = [:title_content, :page_header, :add_thing_button]
messages.each { |message| send message }
end

Expand Down
Loading

0 comments on commit c4ce908

Please sign in to comment.