nakajima-fixjour 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,9 @@
1
1
  module Fixjour
2
2
  class << self
3
+ include Definitions
4
+
3
5
  attr_accessor :allow_redundancy
4
-
6
+
5
7
  # The list of classes that have builders defined.
6
8
  def builders
7
9
  @builders ||= Set.new
@@ -19,19 +21,19 @@ module Fixjour
19
21
  klass.new(options.merge(overrides))
20
22
  end
21
23
  end
22
-
24
+
23
25
  name = name_for(klass)
24
-
26
+
25
27
  define_create(name)
26
28
  define_valid_attributes(name)
27
29
  end
28
-
30
+
29
31
  # Adds builders to Fixjour.
30
32
  def evaluate(&block)
31
33
  begin
32
34
  module_eval(&block)
33
35
  rescue NameError => e
34
- if evaluator.respond_to?(e.name)
36
+ if e.name && evaluator.respond_to?(e.name)
35
37
  raise NonBlockBuilderReference.new("You must use a builder block in order to reference other Fixjour creation methods.")
36
38
  else
37
39
  raise e
@@ -46,9 +48,9 @@ module Fixjour
46
48
  when String, Symbol then builders.map(&:name).include?(builder)
47
49
  end
48
50
  end
49
-
51
+
50
52
  private
51
-
53
+
52
54
  # Registers a class' builder. This allows us to make sure
53
55
  # redundant builders aren't defined, which can lead to confusion
54
56
  # when trying to figure out where objects are being created.
@@ -57,53 +59,11 @@ module Fixjour
57
59
  raise RedundantBuilder.new("You already defined a builder for #{klass.inspect}")
58
60
  end
59
61
  end
60
-
62
+
61
63
  def name_for(klass)
62
64
  klass.name.underscore
63
65
  end
64
-
65
- # Defines the new_* method
66
- def define_new(klass, &block)
67
- name = name_for(klass)
68
- define_method("new_#{name}") do |*args|
69
- overrides = OverridesHash.new(args.first || { })
70
-
71
- args = case block.arity
72
- when 1 then [overrides]
73
- when 2 then [MergingProxy.new(klass, overrides), overrides]
74
- end
75
-
76
- result = block.bind(self).call(*args)
77
- result
78
- end
79
- end
80
-
81
- # Defines the create_* method
82
- def define_create(name)
83
- define_method("create_#{name}") do |*args|
84
- model = send("new_#{name}", *args)
85
- model.save!
86
- model
87
- end
88
- end
89
-
90
- # Defines the valid_*_attributes method
91
- def define_valid_attributes(name)
92
- define_method("valid_#{name}_attributes") do |*args|
93
- if instance_variable_get("@__valid_#{name}_attrs").nil?
94
- valid_attributes = send("new_#{name}").attributes
95
- valid_attributes.delete_if { |key, value| value.nil? }
96
- instance_variable_set("@__valid_#{name}_attrs", valid_attributes)
97
- end
98
66
 
99
- overrides = args.extract_options!
100
- attrs = instance_variable_get("@__valid_#{name}_attrs").merge(overrides)
101
- attrs.stringify_keys!
102
- attrs.make_indifferent!
103
- attrs
104
- end
105
- end
106
-
107
67
  # Anonymous class used for reflecting on current Fixjour state.
108
68
  def evaluator
109
69
  @evaluator ||= begin
@@ -0,0 +1,36 @@
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!)
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
+ if instance_variable_get("@__valid_#{name}_attrs").nil?
23
+ valid_attributes = send("new_#{name}").attributes
24
+ valid_attributes.delete_if { |key, value| value.nil? }
25
+ instance_variable_set("@__valid_#{name}_attrs", valid_attributes)
26
+ end
27
+
28
+ overrides = args.extract_options!
29
+ attrs = instance_variable_get("@__valid_#{name}_attrs").merge(overrides)
30
+ attrs.stringify_keys!
31
+ attrs.make_indifferent!
32
+ attrs
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,7 +1,11 @@
1
1
  module Fixjour
2
- # Raised when a builder returns an invalid object
2
+ # Raised when a builder returns an invalid object.
3
3
  class InvalidBuilder < StandardError; end
4
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
+
5
9
  # Raised when a builder returns an object of the wrong type
6
10
  class WrongBuilderType < StandardError; end
7
11
 
@@ -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 [overrides]
23
+ when 2 then [MergingProxy.new(klass, overrides), overrides]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -12,8 +12,8 @@ module Fixjour
12
12
  # Allows for processing of the overrides hash. Deletes
13
13
  # the option when it's present, then yields the value.
14
14
  def process(option)
15
- if value = delete(option)
16
- yield value if block_given?
15
+ delete(option).tap do |value|
16
+ yield value if value and block_given?
17
17
  end
18
18
  end
19
19
  end
@@ -3,6 +3,10 @@ module Fixjour
3
3
  klass.extend(RedundancyChecker)
4
4
  end
5
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.
6
10
  module RedundancyChecker
7
11
  BUILDER_METHOD_PATTERN = /^(new|create|valid)_(\w+)(_attributes)?$/
8
12
 
@@ -7,20 +7,40 @@ module Fixjour
7
7
  # * The creation methods return objects of the proper type
8
8
  def verify!
9
9
  builders.each do |klass|
10
- result = evaluator.send("new_#{name_for(klass)}")
10
+ result = new_record(klass)
11
11
 
12
12
  unless result.valid?
13
- raise InvalidBuilder.new("The builder for #{klass} returns an invalid object: #{result.errors.inspect}")
13
+ error(klass, InvalidBuilder, "returns an invalid object: #{result.errors.inspect}")
14
14
  end
15
15
 
16
16
  unless result.new_record?
17
- raise BuilderSavedRecord.new("The builder for #{klass} must not save the object")
17
+ error(klass, BuilderSavedRecord, "must return a new record")
18
18
  end
19
19
 
20
20
  unless result.is_a?(klass)
21
- raise WrongBuilderType.new("The builder for #{klass} must return instance of #{klass}")
21
+ error(klass, WrongBuilderType, "must return an instance of #{klass}")
22
+ end
23
+
24
+ result.save!
25
+
26
+ unless new_record(klass).valid?
27
+ msg = ""
28
+ msg << "returns invalid an invalid object after another object has been saved.\n"
29
+ msg << "This could be caused by a validates_uniqueness_of validation in your model.\n"
30
+ msg << "Use something like the faker gem to alleviate this issue."
31
+ error(klass, DangerousBuilder, msg)
22
32
  end
23
33
  end
24
34
  end
35
+
36
+ private
37
+
38
+ def error(klass, exception, msg)
39
+ raise exception.new("The builder for #{klass} #{msg} ")
40
+ end
41
+
42
+ def new_record(klass)
43
+ evaluator.send("new_#{name_for(klass)}")
44
+ end
25
45
  end
26
46
  end
data/lib/fixjour.rb CHANGED
@@ -3,11 +3,14 @@ $LOAD_PATH << File.dirname(__FILE__)
3
3
  require 'rubygems'
4
4
  require 'activerecord'
5
5
  require 'core_ext/hash'
6
+ require 'core_ext/object'
6
7
  require 'fixjour/merging_proxy'
7
8
  require 'fixjour/redundant_check'
8
9
  require 'fixjour/overrides_hash'
9
10
  require 'fixjour/verify'
10
11
  require 'fixjour/errors'
12
+ require 'fixjour/generator'
13
+ require 'fixjour/definitions'
11
14
  require 'fixjour/builders'
12
15
 
13
16
  # This method is just for prettiness
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nakajima-fixjour
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pat Nakajima
@@ -34,11 +34,13 @@ files:
34
34
  - lib/core_ext/hash.rb
35
35
  - lib/fixjour
36
36
  - lib/fixjour/builders.rb
37
- - lib/fixjour/merging_proxy.rb
37
+ - lib/fixjour/definitions.rb
38
38
  - lib/fixjour/errors.rb
39
- - lib/fixjour/verify.rb
40
- - lib/fixjour/redundant_check.rb
39
+ - lib/fixjour/generator.rb
40
+ - lib/fixjour/merging_proxy.rb
41
41
  - lib/fixjour/overrides_hash.rb
42
+ - lib/fixjour/redundant_check.rb
43
+ - lib/fixjour/verify.rb
42
44
  - lib/fixjour.rb
43
45
  has_rdoc: true
44
46
  homepage: http://github.com/nakajima/fixjour