Skip to content

Commit 5752c58

Browse files
authored
V2 (#20)
* beginning refactor * more v2 rewrite, removing need for inheritance * v2 tweaks to rewrite logic * refactors base run() method, and early prop setting * rips out old shitty tests, stubs out new ones for logic built so far: * v2 clean up. Moves core logic changes to lib/. Refactors and fixes base rspec implementation and tests * refactors spec tests in output spec * resolves #14 updates readme for V2 * readme updated, again * v2, refactors all helper classes in spec to mutant_spec_helpers.rb' * adds tests and logic for running all the mutations's instance validation functions? * tweaks spec fixtures * defines required_attr method for mutation classes to define which properties the mutaiton should enforce are present. * Adds specs for handling required field setting, in both raise_or_error is true and false * resolves #27 by updating gemsepc * removes old v1 errors that are not needed * adds YARD documentation to output.rb * Adds more YARD documentation * Delete .byebug_history * removes byebug from depdencies
1 parent 4a0c0e0 commit 5752c58

20 files changed

+563
-230
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
/pkg/
77
/spec/reports/
88
/tmp/
9+
/.idea
910

1011
# rspec failure tracking
1112
.rspec_status
13+
.byebug_history

.rakeTasks

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Settings><!--This file was automatically generated by Ruby plugin.
3+
You are allowed to:
4+
1. Remove rake task
5+
2. Add existing rake tasks
6+
To add existing rake tasks automatically delete this file and reload the project.
7+
--><RakeGroup description="" fullCmd="" taksId="rake" /></Settings>

Gemfile.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ DEPENDENCIES
3232
ruby-mutant!
3333

3434
BUNDLED WITH
35-
1.16.6
35+
1.17.3

README.md

+46-26
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,66 @@ gem 'ruby-mutant'
1515

1616
Ruby Mutant is a lightweight mutations library to help you encapsulate your business logic. With Ruby Mutant you can easily add executable code with validation, helping you decouple important logic from your application.
1717

18-
To create a mutation, subclass `MutatantBase`
18+
To create a mutation from a ruby object, include the module `include Mutatant`
19+
1920

2021
```ruby
2122
require "mutant"
2223

23-
class ProductCreatedMutation < MutantBase
24-
25-
#Define required attributes for this mutation to execute
26-
required do
27-
{
28-
name: String,
29-
address: String,
30-
product: Product,
31-
name: String
32-
}
33-
end
34-
35-
validate do
36-
[:validate_name?]
37-
end
24+
class RecipeCreatedMutation
25+
include Mutant
3826

3927
#Define custom validators for our attributes
4028
def validate_name?
4129
true
4230
end
4331

44-
# Requried, this will execute our new mutation.
45-
def self.run(*args)
46-
super
47-
input = args[0].to_h
48-
49-
# Put all our mutation logic here!
50-
input[:product].on_sale = true
51-
52-
# Output is generated by the Mutator, contains our errors and success? of validation.
53-
@output
32+
# Required, this will execute our new mutation.
33+
def execute(args)
34+
# here, recipe is passe into out Class.run(recipe=Recipe.new) method
35+
if recipe.difficulty == 'godlike'
36+
recipe_service.send_alert_new_super_recipe_confirmation()
37+
end
5438
end
5539
end
5640
```
5741

42+
---
43+
To run a mutation definition:
44+
```ruby
45+
# run() excepts any number of parameters that you want to pass into your mutation
46+
output = RecipeCreatedMutation.run(obj: obj1, obj2: obj2, ....)
47+
```
48+
49+
Every mutation execution will return an `output` object. This object contains information on the
50+
mutation execution, and errors occurred or any metadata that's needed to be returned from the mutation,
51+
in the form of a hash.
52+
53+
Any meta data that needs to be returned can be added to the output object using the
54+
helper method inside your mutation:
55+
56+
```ruby
57+
class RecipeCreatedMutation
58+
include Mutant
59+
...
60+
def execute(args)
61+
output.add_meta(:test, 'value')
62+
end
63+
64+
...
65+
66+
output = RecipeCreatedMutation.run()
67+
output.meta[:test] # >> 'value'
68+
69+
```
70+
71+
```ruby
72+
output = RecipeCreatedMutation.run(obj: obj1)
73+
output.success?
74+
output.errors
75+
output.meta
76+
```
77+
5878

5979

6080
## Contributing

lib/mutant.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
require "ruby-mutant/version"
2-
require "ruby-mutant/mutant_base"
2+
require "ruby-mutant/base"
33

44
module Mutant
55
end

lib/ruby-mutant/base.rb

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
require 'ruby-mutant/exceptions/mutation_validation_exception'
2+
require 'ruby-mutant/exceptions/mutation_setup_exception'
3+
require 'ruby-mutant/exceptions/mutation_missing_required_var_exception'
4+
require 'ruby-mutant/output'
5+
require "bundler/setup"
6+
7+
8+
module Mutant
9+
attr_accessor :output
10+
11+
def self.included(klass)
12+
klass.extend(ClassMethods)
13+
end
14+
15+
module ClassMethods
16+
def required_attr(*attrs)
17+
puts 'Mutant::required_attr'
18+
# This defines a method called required_attr that will
19+
# return an array of symbols for all the properties of the
20+
# mutation that we should have defined.
21+
define_method(:required_attr) { attrs ||= [] }
22+
end
23+
24+
# The entry point, main method that will be execute any mutation logic
25+
#
26+
# == Parameters:
27+
# args::
28+
# A hash of inputs suppplied when the user runs the mutation.
29+
# i.e. MyMutation.run(name: 'jon', house: 'stark')
30+
#
31+
# == Returns:
32+
# An instance of `Mutant::Output`. This object defines all
33+
# errors, metadata and success definition of the mutation
34+
def run(args = {})
35+
unless args.has_key?(:raise_on_error)
36+
args[:raise_on_error] = true
37+
end
38+
args[:raise_on_error].freeze
39+
40+
puts 'Mutant::self.run(*args) '
41+
obj = new(args)
42+
43+
# Ensure the mutation has the correct method
44+
unless obj.respond_to?(:execute)
45+
raise MutationSetupException.new(msg='Missing execute method')
46+
end
47+
48+
# 1. We want to run the validators first, then determine if we should continue
49+
errs = obj.send(:validate)
50+
obj.output.errors = obj.output.errors + errs
51+
if errs.length > 0
52+
if args[:raise_on_error]
53+
raise MutationSetupException.new(msg='Validation failed')
54+
end
55+
end
56+
57+
# 2. Check to see the mutation has the corresponding inst vars
58+
args.each do |k, val|
59+
puts "Mutant::var check '#{k}', responds? #{obj.respond_to? k.to_sym}"
60+
61+
# First make sure this mutation obj has the correct vars,
62+
# if not, then proceeed to create them
63+
unless obj.respond_to? k.to_sym
64+
# create the attr_accessor for the missing vars
65+
obj.class.send(:define_method, "#{k}=".to_sym) do |value|
66+
instance_variable_set("@" + k.to_s, value)
67+
end
68+
obj.class.send(:define_method, k.to_sym) do
69+
instance_variable_get("@" + k.to_s)
70+
end
71+
end
72+
73+
# 3. Propagate the values from the mutation props to the class
74+
obj.send("#{k}=".to_sym, val)
75+
end
76+
77+
# 3 If this instance defines :required_attr
78+
if obj.respond_to? :required_attr
79+
required_attr_errors = obj.send(:check_required_attrs)
80+
unless required_attr_errors.length == 0
81+
# We need to handle any errors we get back from our
82+
# required_attr validator
83+
obj.output.errors += required_attr_errors
84+
if args[:raise_on_error]
85+
raise required_attr_errors[0]
86+
end
87+
end
88+
end
89+
90+
# 4. Run execute method to run mutation logic
91+
obj.execute(args)
92+
# Return out Output obj, with all meta data regarding the ran mutation
93+
obj.output
94+
end
95+
end
96+
97+
def initialize(*args)
98+
@output = Output.new
99+
end
100+
101+
private
102+
103+
# This will run all ou validation functions on our mutation class.
104+
# This will return an array of MutationValidationException, to the class method, run()
105+
#
106+
# == Parameters:
107+
#
108+
# == Returns:
109+
# errors. An array of errors representing all validation methods that have failed.
110+
# (defaults to `[]`)
111+
def validate
112+
errors = []
113+
self.public_methods.each do |m|
114+
if m.to_s.start_with?('validate_') && m.to_s.end_with?('?')
115+
# execute validation method
116+
res = self.send(m)
117+
118+
# unless the response is truthy
119+
unless res
120+
errors << MutationValidationException.new(msg='Validator has returned false', validator=m)
121+
end
122+
end
123+
end
124+
errors
125+
end
126+
127+
# Checks to see if any `required_attr` has been set, if so check to see if each one
128+
# is defined in either the .run() definition or the mutation's `attr_accesor`.
129+
# TODO need to also check that these required attributes have values
130+
#
131+
# == Parameters:
132+
#
133+
# == Returns:
134+
# An array of errors, of type `MutationMissingRequiredVarException`, for each
135+
# missing required attribute.
136+
def check_required_attrs
137+
# In this we need to compare what we define in
138+
# required_attr(*attrs) against what we have defined in
139+
# the mutation vs what we pass into the run() definition
140+
errors = []
141+
puts 'Mutant::check_required_attrs'
142+
self.required_attr.each do |attr|
143+
if !self.respond_to?(attr)
144+
# Our attribute is not defined on our mutation class
145+
# So we will build the error to return to the run()
146+
# method, which can determine how we proceed
147+
err = MutationMissingRequiredVarException.new(
148+
msg="A property that is marked as required is not defined on the mutation: #{attr}",
149+
prop=attr)
150+
errors << err
151+
end
152+
end
153+
errors
154+
end
155+
156+
end

lib/ruby-mutant/exceptions/mutation_duplicate_attr_exception.rb

-7
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class MutationMissingRequiredVarException < StandardError
2+
attr_reader :prop
3+
def initialize(msg='MutationMissingRequiredVarException', prop=nil)
4+
@prop = prop
5+
super("#{msg} - #{prop}")
6+
end
7+
end

lib/ruby-mutant/exceptions/mutation_prop_undefined.rb

-7
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class MutationSetupException < StandardError
2+
def initialize(msg='MutationSetupException')
3+
super(msg)
4+
end
5+
end

0 commit comments

Comments
 (0)