initial commit, yay super-easy form objects

This commit is contained in:
John Bintz 2013-07-09 17:27:58 -04:00
commit d910e0e837
12 changed files with 336 additions and 0 deletions

17
.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp

4
Gemfile Normal file
View File

@ -0,0 +1,4 @@
source 'https://rubygems.org'
# Specify your gem's dependencies in carapace.gemspec
gemspec

22
LICENSE.txt Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2013 John Bintz
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

51
README.md Normal file
View File

@ -0,0 +1,51 @@
# CANDY WRAPPER
Use form objects with ease. Plugs into `inherited_resources` easily:
``` ruby
# app/models/database_object.rb
class DatabaseObject < Persistence::Base
# ... persistence and relationships only ...
end
```
``` ruby
# app/controllers/database_objects_controller.rb
class DatabaseObjectsController < ApplicationController
inherit_resources
# for the parts of inherited_resources that actually persist models,
# ensure that persistence takes placed within a CandyWrapper::ModelWrapper
# form object. Those respond to save, assign_attributes, and update_attributes.
wrap_in_form_object!
end
```
``` ruby
# app/form_objects/database_object_form_object.rb
class DatabaseObjectFormObject < CandyWrapper::ModelWrapper
def complex_parameter=(database_object, parameter_value)
# do complex formatting here, probably for nested objects
# database_object will be saved by this point
end
# perform these actions before database_object is saved
before_wrapped_save :process_first_parameter
def process_first_parameter=(database_object, parameter_value)
# use normal accessors to set properties on database_object, it will be
# saved when all before_wrapped_saved methods are run
end
# there is no guarantee in the order that these will run, don't make them
# depend on each other!
end
```
## Coming soon!
* Form objects that hang off of models as if they were relationships
* A better readme!

1
Rakefile Normal file
View File

@ -0,0 +1 @@
require "bundler/gem_tasks"

26
candy_wrapper.gemspec Normal file
View File

@ -0,0 +1,26 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'candy_wrapper/version'
Gem::Specification.new do |spec|
spec.name = "candy_wrapper"
spec.version = CandyWrapper::VERSION
spec.authors = ["John Bintz"]
spec.email = ["john@coswellproductions.com"]
spec.description = %q{Use form objects with ease.}
spec.summary = %q{Use form objects with ease.}
spec.homepage = ""
spec.license = "MIT"
spec.files = `git ls-files`.split($/)
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]
spec.add_development_dependency "bundler", "~> 1.3"
spec.add_development_dependency "rake"
spec.add_development_dependency "rspec"
spec.add_dependency 'activesupport'
end

4
lib/candy_wrapper.rb Normal file
View File

@ -0,0 +1,4 @@
require "candy_wrapper/version"
require "candy_wrapper/model_wrapper"
require "candy_wrapper/inherited_resources"

View File

@ -0,0 +1,59 @@
require 'active_support/concern'
module CandyWrapper
module InheritedResources
extend ActiveSupport::Concern
module ClassMethods
def __candy_wrapper__
@__candy_wrapper__
end
def wrap_in_form_object(options = {})
@__candy_wrapper__ = {
with: resource_class.name + "FormObject",
only: [ :create, :update ]
}.merge(options)
end
def wrap_in_form_object!(*args)
wrap_in_form_object(*args)
end
end
protected
def build_resource
get_resource_ivar || set_resource_ivar(
if self.class.__candy_wrapper__[:only].include?(action_name.to_sym)
object = end_of_association_chain.send(method_for_build)
else
super
end
)
end
def create_resource(object)
if self.class.__candy_wrapper__[:only].include?(action_name.to_sym)
wrapped_object = candy_wrapper_class.new(object, *resource_params)
wrapped_object.save
else
super
end
end
def update_resource(object, attributes)
if self.class.__candy_wrapper__[:only].include?(action_name.to_sym)
wrapped_object = candy_wrapper_class.new(object)
wrapped_object.update_attributes(*attributes)
else
super
end
end
def candy_wrapper_class
@__candy_wrapper_class__ ||= self.class.__candy_wrapper__[:with].constantize
end
end
end

View File

@ -0,0 +1,94 @@
require 'delegate'
module CandyWrapper
class ModelWrapper < SimpleDelegator
def self.inherited(klass)
klass.send(:extend, ClassMethods)
if klass.name && original = klass.name[/^(.*)FormObject$/, 1]
klass.wraps original.constantize
end
end
module ClassMethods
def wraps(klass = nil)
if klass
@__wraps__ = klass
else
@__wraps__
end
end
def before_wrapped_save(*args)
@__before_wrapped_save__ ||= []
if args.empty?
@__before_wrapped_save__
else
@__before_wrapped_save__ += args
end
end
end
def __wraps__
self.class.wraps
end
def initialize(object, params = {})
@__object__ = object
assign_attributes(params)
end
def __getobj__
@__object__
end
def assign_attributes(attributes)
@__params__ = attributes.dup
end
def update_attributes(attributes)
assign_attributes(attributes)
save
end
def save
self.class.before_wrapped_save.each do |before|
send("#{before}=", @__object__, @__params__[before])
end
@__object__.assign_attributes(__object_params__)
if result = @__object__.save
setters_and_setter_params.each do |setter, setter_param|
send(setter, @__object__, @__params__[setter_param])
end
end
end
def __object_params__
object_params = @__params__.dup
setter_params.each { |key| object_params.delete(key) }
object_params
end
def setter_params
@__setter_params__ ||= setters.collect { |param| param.gsub('=', '').to_sym }
end
def setters
@__setters__ ||= (my_instance_methods.collect(&:to_s).find_all { |param| param[/=\Z/] } - self.class.before_wrapped_save)
end
def setters_and_setter_params
setters.zip(setter_params)
end
def my_instance_methods
@my_instance_methods ||= (self.class.instance_methods - ModelWrapper.instance_methods)
end
end
end

View File

@ -0,0 +1,3 @@
module CandyWrapper
VERSION = "0.0.1"
end

View File

@ -0,0 +1,51 @@
require 'spec_helper'
require 'candy_wrapper/model_wrapper'
describe CandyWrapper::ModelWrapper do
describe '#update_attributes' do
let(:model_class) {
Class.new do
attr_reader :saved, :attributes
def save
@saved = true
end
def assign_attributes(attributes)
@attributes = attributes
end
end
}
let(:wrap_class) {
klass = Class.new(CandyWrapper::ModelWrapper) do
attr_reader :resource, :value
def setter=(resource, value)
@resource, @value = resource, value
end
end
klass.send(:wraps, model_class)
klass
}
let(:setter_value) { 'setter value' }
let(:base_attributes) { { :base => 'base' } }
let(:attributes) { { :setter => setter_value }.merge(base_attributes) }
it 'should save the model and trigger the setters on the wrapper' do
model = model_class.new
wrap = wrap_class.new(model)
wrap.update_attributes(attributes)
model.saved.should be_true
model.attributes.should be == base_attributes
wrap.resource.should == model
wrap.value.should == setter_value
end
end
end

4
spec/spec_helper.rb Normal file
View File

@ -0,0 +1,4 @@
require 'rspec'
$: << File.expand_path('../../lib')