pat-maddox-fixjour 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/core_ext/hash.rb +9 -0
- data/lib/core_ext/object.rb +6 -0
- data/lib/fixjour/builders.rb +80 -0
- data/lib/fixjour/counter.rb +30 -0
- data/lib/fixjour/definitions.rb +30 -0
- data/lib/fixjour/deprecation.rb +24 -0
- data/lib/fixjour/errors.rb +33 -0
- data/lib/fixjour/generator.rb +27 -0
- data/lib/fixjour/merging_proxy.rb +46 -0
- data/lib/fixjour/overrides_hash.rb +20 -0
- data/lib/fixjour/redundant_check.rb +29 -0
- data/lib/fixjour/verify.rb +54 -0
- data/lib/fixjour.rb +25 -0
- metadata +96 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module Fixjour
|
|
2
|
+
def counter(key=nil)
|
|
3
|
+
Counter.counter(key)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
include Definitions
|
|
8
|
+
|
|
9
|
+
attr_accessor :allow_redundancy
|
|
10
|
+
|
|
11
|
+
# The list of classes that have builders defined.
|
|
12
|
+
def builders
|
|
13
|
+
@builders ||= Set.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# This method should always return a valid instance of
|
|
17
|
+
# a model object.
|
|
18
|
+
def define_builder(klass, options={}, &block)
|
|
19
|
+
add_builder(klass)
|
|
20
|
+
|
|
21
|
+
if block_given?
|
|
22
|
+
define_new(klass, &block)
|
|
23
|
+
else
|
|
24
|
+
define_new(klass) do |proxy, overrides|
|
|
25
|
+
proxy.new(options.merge(overrides))
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
name = name_for(klass)
|
|
30
|
+
|
|
31
|
+
define_create(name)
|
|
32
|
+
define_valid_attributes(name)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Adds builders to Fixjour.
|
|
36
|
+
def evaluate(&block)
|
|
37
|
+
begin
|
|
38
|
+
module_eval(&block)
|
|
39
|
+
rescue NameError => e
|
|
40
|
+
if e.name && evaluator.respond_to?(e.name)
|
|
41
|
+
raise NonBlockBuilderReference.new("You must use a builder block in order to reference other Fixjour creation methods.")
|
|
42
|
+
else
|
|
43
|
+
raise e
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Checks to see whether or not a builder is defined. Duh.
|
|
49
|
+
def builder_defined?(builder)
|
|
50
|
+
case builder
|
|
51
|
+
when Class then builders.include?(builders)
|
|
52
|
+
when String, Symbol then builders.map(&:name).include?(builder)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# Registers a class' builder. This allows us to make sure
|
|
59
|
+
# redundant builders aren't defined, which can lead to confusion
|
|
60
|
+
# when trying to figure out where objects are being created.
|
|
61
|
+
def add_builder(klass)
|
|
62
|
+
unless builders.add?(klass) or Fixjour.allow_redundancy
|
|
63
|
+
raise RedundantBuilder.new("You already defined a builder for #{klass.inspect}")
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def name_for(klass)
|
|
68
|
+
klass.name.underscore
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Anonymous class used for reflecting on current Fixjour state.
|
|
72
|
+
def evaluator
|
|
73
|
+
@evaluator ||= begin
|
|
74
|
+
klass = Class.new
|
|
75
|
+
klass.send :include, self
|
|
76
|
+
klass.new
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Fixjour
|
|
2
|
+
class Counter
|
|
3
|
+
class << self
|
|
4
|
+
def reset(key=nil)
|
|
5
|
+
key ? counters.delete(key) : counters.clear
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def counter(key)
|
|
9
|
+
counters[key] ||= new
|
|
10
|
+
counters[key].next
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def counters
|
|
16
|
+
@counters ||= {}
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
reset
|
|
21
|
+
|
|
22
|
+
def initialize
|
|
23
|
+
@value = 0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def next
|
|
27
|
+
@value += 1
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Fixjour
|
|
2
|
+
module Definitions
|
|
3
|
+
# Defines the new_* method
|
|
4
|
+
def define_new(klass, &block)
|
|
5
|
+
define_method("new_#{name_for(klass)}") do |*args|
|
|
6
|
+
Generator.new(klass, block).call(self, args.extract_options!.symbolize_keys!)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Defines the create_* method
|
|
11
|
+
def define_create(name)
|
|
12
|
+
define_method("create_#{name}") do |*args|
|
|
13
|
+
model = send("new_#{name}", *args)
|
|
14
|
+
model.save!
|
|
15
|
+
model
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Defines the valid_*_attributes method
|
|
20
|
+
def define_valid_attributes(name)
|
|
21
|
+
define_method("valid_#{name}_attributes") do |*args|
|
|
22
|
+
valid_attributes = send("new_#{name}", *args).attributes
|
|
23
|
+
valid_attributes.delete_if { |key, value| value.nil? }
|
|
24
|
+
valid_attributes.stringify_keys!
|
|
25
|
+
valid_attributes.make_indifferent!
|
|
26
|
+
valid_attributes
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Fixjour
|
|
2
|
+
module Deprecation
|
|
3
|
+
module MergingProxy
|
|
4
|
+
def process(*args)
|
|
5
|
+
raise DeprecatedMergeAttempt.new(<<-END
|
|
6
|
+
You are attempting to call process on the class proxy.
|
|
7
|
+
This behavior was recently deprecated. In order to process
|
|
8
|
+
the overrides hash, pass two block arguments:
|
|
9
|
+
|
|
10
|
+
define_builder(Foo) do |klass, overrides|
|
|
11
|
+
overrides.process(:bar) do |bar|
|
|
12
|
+
overrides[:bar] = 'overridden'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
klass.new(:name => 'fizz')
|
|
16
|
+
end
|
|
17
|
+
END
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
Fixjour::MergingProxy.send :include, Fixjour::Deprecation::MergingProxy
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Fixjour
|
|
2
|
+
# Raised when a builder returns an invalid object.
|
|
3
|
+
class InvalidBuilder < StandardError; end
|
|
4
|
+
|
|
5
|
+
# Raised when a builder will return an invalid object
|
|
6
|
+
# due to a validates_uniqueness_of validation.
|
|
7
|
+
class DangerousBuilder < StandardError; end
|
|
8
|
+
|
|
9
|
+
# Raised when a builder returns an object that cannot
|
|
10
|
+
# be saved the database.
|
|
11
|
+
class UnsavableBuilder < StandardError; end
|
|
12
|
+
|
|
13
|
+
# Raised when a builder returns an object of the wrong type
|
|
14
|
+
class WrongBuilderType < StandardError; end
|
|
15
|
+
|
|
16
|
+
# Raised when a builder block saves the object.
|
|
17
|
+
class BuilderSavedRecord < StandardError; end
|
|
18
|
+
|
|
19
|
+
# Raised when a Fixjour creation method is called in
|
|
20
|
+
# the wrong context.
|
|
21
|
+
class NonBlockBuilderReference < StandardError; end
|
|
22
|
+
|
|
23
|
+
# Raised when a builder is defined for a class that already
|
|
24
|
+
# has one.
|
|
25
|
+
class RedundantBuilder < StandardError; end
|
|
26
|
+
|
|
27
|
+
# Raised when a builder is defined with one block argument and
|
|
28
|
+
# the user assumes that it's the overrides hash. This used to
|
|
29
|
+
# be the standard behavior, but now blocks with one argument
|
|
30
|
+
# are passed the class proxy, and getting access to the overrides
|
|
31
|
+
# hash requires you pass two block arguments.
|
|
32
|
+
class DeprecatedMergeAttempt < StandardError; end
|
|
33
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Fixjour
|
|
2
|
+
# This generates a new instance of a model object for
|
|
3
|
+
# the new_[model] method.
|
|
4
|
+
class Generator
|
|
5
|
+
attr_reader :klass, :block
|
|
6
|
+
|
|
7
|
+
def initialize(klass, block)
|
|
8
|
+
@klass, @block = klass, block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(context, overrides={})
|
|
12
|
+
overrides = OverridesHash.new(overrides)
|
|
13
|
+
result = block.bind(context).call(*args(overrides))
|
|
14
|
+
case result
|
|
15
|
+
when Hash then klass.new(result.merge(overrides))
|
|
16
|
+
else result
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def args(overrides)
|
|
21
|
+
case block.arity
|
|
22
|
+
when 1 then [MergingProxy.new(klass, overrides)]
|
|
23
|
+
when 2 then [MergingProxy.new(klass, overrides), overrides]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Fixjour
|
|
2
|
+
# Proxy passed to builder blocks as first argument when a builder
|
|
3
|
+
# block takes two arguments. Automatically merges overrides when
|
|
4
|
+
# the #new method is called.
|
|
5
|
+
class MergingProxy
|
|
6
|
+
instance_methods.each { |m| undef_method(m) unless m =~ /__|inspect/ }
|
|
7
|
+
|
|
8
|
+
def initialize(klass, overrides)
|
|
9
|
+
@klass = klass
|
|
10
|
+
@overrides = overrides
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def protected(*attrs)
|
|
14
|
+
attrs = attrs.empty? ?
|
|
15
|
+
@protected :
|
|
16
|
+
@protected = attrs
|
|
17
|
+
Set.new(attrs)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def new(defaults={})
|
|
21
|
+
attrs = defaults.merge(@overrides)
|
|
22
|
+
accessible, inaccessible = partition(attrs)
|
|
23
|
+
|
|
24
|
+
returning @klass.new(accessible) do |instance|
|
|
25
|
+
inaccessible.each do |key,val|
|
|
26
|
+
instance.send("#{key}=", val)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def method_missing(sym, *args, &block)
|
|
32
|
+
@klass.respond_to?(sym) ? @klass.send(sym, *args, &block) : super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def partition(attrs)
|
|
38
|
+
accessible = attrs.keys.inject({ }) do |m, key|
|
|
39
|
+
next m if protected.include?(key)
|
|
40
|
+
m[key] = attrs.delete(key)
|
|
41
|
+
m
|
|
42
|
+
end
|
|
43
|
+
[accessible, attrs]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Fixjour
|
|
2
|
+
# The overrides hash passed into each of the Fixjour
|
|
3
|
+
# methods is wrapped in this class to make the delete
|
|
4
|
+
# method private and add the ability to process.
|
|
5
|
+
class OverridesHash < Hash
|
|
6
|
+
private :delete
|
|
7
|
+
|
|
8
|
+
def initialize(hash)
|
|
9
|
+
replace(hash)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Allows for processing of the overrides hash. Deletes
|
|
13
|
+
# the option when it's present, then yields the value.
|
|
14
|
+
def process(option)
|
|
15
|
+
delete(option).tap do |value|
|
|
16
|
+
yield value if value and block_given?
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Fixjour
|
|
2
|
+
def self.included(klass)
|
|
3
|
+
klass.extend(RedundancyChecker)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
# Uses method_added hook to make sure no redundant builder methods
|
|
7
|
+
# get defined after a builder has already created them. For example,
|
|
8
|
+
# if you have a Comment builder, this hook will ensure that any attempt
|
|
9
|
+
# to define a #new_comment method will raise an exception.
|
|
10
|
+
module RedundancyChecker
|
|
11
|
+
BUILDER_METHOD_PATTERN = /^(new|create|valid)_(\w+)(_attributes)?$/
|
|
12
|
+
|
|
13
|
+
def method_added(sym)
|
|
14
|
+
name = sym.to_s
|
|
15
|
+
|
|
16
|
+
if klass_name = get_klass_name(name)
|
|
17
|
+
if Fixjour.builder_defined?(klass_name)
|
|
18
|
+
raise RedundantBuilder.new("You already defined a builder for #{inspect}")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get_klass_name(name)
|
|
24
|
+
if match = name.match(BUILDER_METHOD_PATTERN)
|
|
25
|
+
match[2].classify.gsub(/Attribute$/, '')
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Fixjour
|
|
2
|
+
class << self
|
|
3
|
+
# Checks each builder to ensure that they:
|
|
4
|
+
#
|
|
5
|
+
# * Return valid objects
|
|
6
|
+
# * The new_* methods return new records
|
|
7
|
+
# * The creation methods return objects of the proper type
|
|
8
|
+
def verify!
|
|
9
|
+
builders.each do |klass|
|
|
10
|
+
result = new_record(klass)
|
|
11
|
+
|
|
12
|
+
unless result.valid?
|
|
13
|
+
error(klass, InvalidBuilder, "returns an invalid object: #{result.errors.inspect}")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
unless result.new_record?
|
|
17
|
+
error(klass, BuilderSavedRecord, "must return a new record")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
unless result.is_a?(klass)
|
|
21
|
+
error(klass, WrongBuilderType, "must return an instance of #{klass}")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
klass.transaction do
|
|
25
|
+
begin
|
|
26
|
+
result.save!
|
|
27
|
+
rescue => e
|
|
28
|
+
error(klass, UnsavableBuilder, "raises #{e.inspect} when saved to the database")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
unless new_record(klass).valid?
|
|
32
|
+
msg = ""
|
|
33
|
+
msg << "returns invalid an invalid object after another object has been saved.\n"
|
|
34
|
+
msg << "This could be caused by a validates_uniqueness_of validation in your model.\n"
|
|
35
|
+
msg << "Use something like the faker gem to alleviate this issue."
|
|
36
|
+
error(klass, DangerousBuilder, msg)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
raise ActiveRecord::Rollback
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def new_record(klass)
|
|
45
|
+
evaluator.send("new_#{name_for(klass)}")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def error(klass, exception, msg)
|
|
51
|
+
raise exception.new("The builder for #{klass} #{msg} ")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/fixjour.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
require 'activerecord'
|
|
5
|
+
require 'core_ext/hash'
|
|
6
|
+
require 'core_ext/object'
|
|
7
|
+
require 'fixjour/merging_proxy'
|
|
8
|
+
require 'fixjour/redundant_check'
|
|
9
|
+
require 'fixjour/overrides_hash'
|
|
10
|
+
require 'fixjour/verify'
|
|
11
|
+
require 'fixjour/errors'
|
|
12
|
+
require 'fixjour/generator'
|
|
13
|
+
require 'fixjour/definitions'
|
|
14
|
+
require 'fixjour/counter'
|
|
15
|
+
require 'fixjour/builders'
|
|
16
|
+
require 'fixjour/deprecation'
|
|
17
|
+
|
|
18
|
+
# This method is just for prettiness
|
|
19
|
+
def Fixjour(options={}, &block)
|
|
20
|
+
return Fixjour unless block_given?
|
|
21
|
+
Fixjour.allow_redundancy = options[:allow_redundancy]
|
|
22
|
+
Fixjour.evaluate(&block)
|
|
23
|
+
Fixjour.verify! if options[:verify]
|
|
24
|
+
Fixjour.allow_redundancy = false
|
|
25
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: pat-maddox-fixjour
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.5
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Pat Nakajima
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2008-12-29 00:00:00 -08:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: activerecord
|
|
17
|
+
type: :runtime
|
|
18
|
+
version_requirement:
|
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
- - ">="
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: "0"
|
|
24
|
+
version:
|
|
25
|
+
- !ruby/object:Gem::Dependency
|
|
26
|
+
name: faker
|
|
27
|
+
type: :development
|
|
28
|
+
version_requirement:
|
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: "0"
|
|
34
|
+
version:
|
|
35
|
+
- !ruby/object:Gem::Dependency
|
|
36
|
+
name: acts_as_fu
|
|
37
|
+
type: :development
|
|
38
|
+
version_requirement:
|
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: "0"
|
|
44
|
+
version:
|
|
45
|
+
description:
|
|
46
|
+
email: patnakajima@gmail.com
|
|
47
|
+
executables: []
|
|
48
|
+
|
|
49
|
+
extensions: []
|
|
50
|
+
|
|
51
|
+
extra_rdoc_files: []
|
|
52
|
+
|
|
53
|
+
files:
|
|
54
|
+
- lib/core_ext
|
|
55
|
+
- lib/core_ext/hash.rb
|
|
56
|
+
- lib/core_ext/object.rb
|
|
57
|
+
- lib/fixjour
|
|
58
|
+
- lib/fixjour/builders.rb
|
|
59
|
+
- lib/fixjour/counter.rb
|
|
60
|
+
- lib/fixjour/definitions.rb
|
|
61
|
+
- lib/fixjour/deprecation.rb
|
|
62
|
+
- lib/fixjour/errors.rb
|
|
63
|
+
- lib/fixjour/generator.rb
|
|
64
|
+
- lib/fixjour/merging_proxy.rb
|
|
65
|
+
- lib/fixjour/overrides_hash.rb
|
|
66
|
+
- lib/fixjour/redundant_check.rb
|
|
67
|
+
- lib/fixjour/verify.rb
|
|
68
|
+
- lib/fixjour.rb
|
|
69
|
+
has_rdoc: true
|
|
70
|
+
homepage: http://github.com/nakajima/fixjour
|
|
71
|
+
post_install_message:
|
|
72
|
+
rdoc_options: []
|
|
73
|
+
|
|
74
|
+
require_paths:
|
|
75
|
+
- lib
|
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: "0"
|
|
81
|
+
version:
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: "0"
|
|
87
|
+
version:
|
|
88
|
+
requirements: []
|
|
89
|
+
|
|
90
|
+
rubyforge_project:
|
|
91
|
+
rubygems_version: 1.2.0
|
|
92
|
+
signing_key:
|
|
93
|
+
specification_version: 2
|
|
94
|
+
summary: Object creation methods everyone already has
|
|
95
|
+
test_files: []
|
|
96
|
+
|