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.
@@ -0,0 +1,9 @@
1
+ class Hash
2
+ # Makes a hash able to be accessed via symbol or string keys.
3
+ def make_indifferent!
4
+ keys_values = self.dup
5
+ indifferent = Hash.new { |h,k| h[k.to_s] if Symbol === k }
6
+ replace(indifferent)
7
+ merge!(keys_values)
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ class Object
2
+ def tap
3
+ yield self
4
+ self
5
+ end
6
+ end
@@ -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
+