olfactory 0.0.1

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/.gitignore ADDED
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ Gemfile.lock
30
+ .ruby-version
31
+ .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 StreetEasy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ Olfactory
2
+ ==========
3
+
4
+ ### Introduction
5
+
6
+ Olfactory is a factory extension for creating complex object sets, as a supplement to *factory_girl*, *fabrication*, or other factories.
7
+
8
+ It introduces the concept of **templates**: an abstract group of objects. These can be used to make test setup much quicker and easier.
9
+
10
+ Templates are not intended to replace most factories. They are most useful when:
11
+ - Subjects are weakly related or non-relational (e.g. not joined by ActiveRecord associations)
12
+ - You require a collection of objects that are not directly related (or easily built using factories)
13
+ - You'd prefer short-hand notation, or to abstract Factory implementation out of your tests
14
+ - e.g. Define a set of generic factories other programmers can use, without requiring them to understand the underlying details
15
+
16
+ For example, given:
17
+ ```ruby
18
+ context "networkable people" do
19
+ let(:phone_user) { create(:person, :devices => [phone]) }
20
+ let(:phone) { create(:android, apps => [facebook_app_one, twitter_app_one]) } # Factory(:device)
21
+ let(:facebook_phone) { create(:facebook, :platform => :phone) } # Factory(:app)
22
+ let(:twitter_phone) { create(:twitter, :platform => :phone) } # Factory(:app)
23
+
24
+ let(:tablet_user) { create(:person, :devices => [tablet]) }
25
+ let(:tablet) { create(:tablet, apps => [facebook_tablet]) } # Factory(:device)
26
+ let(:facebook_tablet) { create(:facebook, :platform => :tablet) } # Factory(:app)
27
+
28
+ let(:desktop_user) { create(:person, :devices => [desktop]) }
29
+ let(:desktop) { create(:desktop, apps => [twitter_desktop]) } # Factory(:device)
30
+ let(:twitter_desktop) { create(:twitter, :platform => :desktop) } # Factory(:app)
31
+
32
+ it { expect(phone_user.can_network_with(tablet_user, desktop_user).to be_true }
33
+ end
34
+ ```
35
+
36
+ This is a lot of setup for a simple test case. We could write a bunch of named factories, but then the test logic simply ends up in a factory file rather than our test; not good.
37
+
38
+ Instead, we can use templates to simplify our definitions:
39
+ ```ruby
40
+ context "networkable people" do
41
+ let(:user_group) do
42
+ Olfactory.create_template :user_group do |group|
43
+ group.user :desktop_user { |user| user.phone { |phone| phone.app :facebook, :twitter } }
44
+ group.user :tablet_user { |user| user.tablet { |tablet| tablet.app :facebook } }
45
+ group.user :phone_user { |user| user.desktop { |desktop| desktop.app :twitter } }
46
+ end
47
+ end
48
+
49
+ it { expect(user_group[:desktop_user].can_network_with(user_group[:tablet_user], user_group[:phone_user]).to be_true }
50
+ end
51
+ ```
@@ -0,0 +1,103 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Olfactory
3
+ class Template < Hash
4
+ attr_accessor :definition, :transients, :default_mode
5
+
6
+ def initialize(definition, options = {})
7
+ self.definition = definition
8
+ self.transients = options[:transients] ? options[:transients].clone : {}
9
+ end
10
+
11
+ def build(block, options = {})
12
+ if block # Block can be nil (when we want only defaults)
13
+ if options[:value]
14
+ block.call(self, options[:value])
15
+ else
16
+ block.call(self)
17
+ end
18
+ end
19
+ if options[:defaults].nil? || options[:defaults]
20
+ # Only set defaults if configuration wasn't specified
21
+ self.add_defaults
22
+ end
23
+
24
+ self
25
+ end
26
+
27
+ def method_missing(meth, *args, &block)
28
+ # Explicit fields
29
+ if (definition.t_macros.has_key?(meth) || definition.t_subtemplates.has_key?(meth) || definition.t_items.has_key?(meth))
30
+ if definition.t_macros.has_key?(meth)
31
+ field_type = :macro
32
+ definition_of_field = definition.t_macros[meth]
33
+ field_value = build_macro(definition_of_field, args, block)
34
+ elsif definition.t_subtemplates.has_key?(meth) && !(self.default_mode && self.has_key?(meth))
35
+ field_type = :subtemplate
36
+ definition_of_field = definition.t_subtemplates[meth]
37
+ subtemplate_name = definition_of_field.has_key?(:template) ? definition_of_field[:template] : meth
38
+ field_value = build_subtemplate(Olfactory.templates[subtemplate_name], args, block)
39
+ elsif definition.t_items.has_key?(meth) && !(self.default_mode && self.has_key?(meth))
40
+ field_type = :item
41
+ definition_of_field = definition.t_items[meth]
42
+ field_value = build_item(definition_of_field, args, block)
43
+ end
44
+ # Add field value to template
45
+ if field_type && field_type != :macro
46
+ if definition_of_field[:collection]
47
+ self[meth] = [] if !self.has_key?(meth)
48
+ if field_type == :subtemplate && field_value.class <= Array
49
+ self[meth].concat(field_value)
50
+ else
51
+ self[meth] << field_value
52
+ end
53
+ else
54
+ self[meth] = field_value
55
+ end
56
+ end
57
+ # No field definition
58
+ else
59
+ super # Unknown method
60
+ end
61
+ end
62
+ def transient(name, value)
63
+ self.transients[name] = value if !(self.default_mode && self.transients.has_key?(name))
64
+ end
65
+ def add_defaults
66
+ # Prevents overwrites of custom values by defaults
67
+ self.default_mode = true # Hackish for sure, but its efficient...
68
+
69
+ default_definition = definition.t_default
70
+ if default_definition[:evaluator]
71
+ default_definition[:evaluator].call(self)
72
+ elsif default_definition[:preset_name]
73
+ preset_definition = definition.find_preset_definition(default_definition[:preset_name])
74
+ preset_definition[:evaluator].call(self)
75
+ end
76
+
77
+ self.default_mode = false
78
+ end
79
+ def build_macro(macro_definition, args, block)
80
+ if macro_definition[:evaluator]
81
+ macro_definition[:evaluator].call(self, *args)
82
+ end
83
+ end
84
+ def build_subtemplate(subtemplate_definition, args, block)
85
+ if block
86
+ subtemplate_definition.build(block, :transients => self.transients)
87
+ else
88
+ subtemplate_definition.build_preset((args.count == 1 ? args.first : args), :transients => self.transients)
89
+ end
90
+ end
91
+ def build_item(item_definition, args, block)
92
+ if block
93
+ block.call(*args)
94
+ else
95
+ if item_definition[:evaluator]
96
+ item_definition[:evaluator].call(*args)
97
+ else
98
+ args.count == 1 ? args.first : argsnew
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,82 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Olfactory
3
+ class TemplateDefinition
4
+ attr_accessor :t_items, :t_subtemplates, :t_macros, :t_presets, :t_default
5
+
6
+ def initialize
7
+ self.t_items = {}
8
+ self.t_subtemplates = {}
9
+ self.t_macros = {}
10
+ self.t_presets = {}
11
+ self.t_default = {}
12
+ end
13
+
14
+ def build(block, options = {})
15
+ if options[:preset]
16
+ self.build_preset(options[:preset], options.reject { |k,v| k == :preset })
17
+ else
18
+ new_template = Template.new(self, options)
19
+ new_template.build(block, options)
20
+ end
21
+ new_template
22
+ end
23
+ def build_preset(preset_name, options = {})
24
+ if preset_name.class <= Integer
25
+ # Build multiple
26
+ Array.new(preset_name) { self.build(nil, options) }
27
+ elsif preset_definition = self.find_preset_definition(preset_name)
28
+ # Build single
29
+ new_template = Template.new(self, options)
30
+ preset_block = preset_definition[:evaluator]
31
+ if preset_definition[:preset_name].class == Regexp
32
+ new_template.build(preset_block, options.merge(:value => preset_name))
33
+ else
34
+ new_template.build(preset_block, options)
35
+ end
36
+ else
37
+ raise "Missing preset matching '#{preset_value}' for template!"
38
+ end
39
+ end
40
+
41
+ def find_preset_definition(preset_name)
42
+ preset_definition = self.t_presets[preset_name]
43
+ if preset_definition.nil?
44
+ # Try to find a regexp named preset that matches
45
+ preset_name = self.t_presets.keys.detect { |p_name| p_name.class == Regexp && p_name.match(preset_name.to_s) }
46
+ preset_definition = self.t_presets[preset_name] if preset_name
47
+ end
48
+ preset_definition
49
+ end
50
+
51
+ # Defines a value holding field
52
+ def has_one(name, options = {}, &block)
53
+ self.t_items[name] = { :evaluator => block }.merge(options)
54
+ end
55
+ def has_many(name, options = {}, &block)
56
+ self.t_items[name] = { :evaluator => block, :collection => true }.merge(options)
57
+ end
58
+
59
+ # Defines a hash-holding field
60
+ def embeds_one(name, options = {}, &block)
61
+ self.t_subtemplates[name] = { :evaluator => block }.merge(options)
62
+ end
63
+ def embeds_many(name, options = {}, &block)
64
+ self.t_subtemplates[name] = { :evaluator => block, :collection => true }.merge(options)
65
+ end
66
+
67
+ # Defines a macro
68
+ def macro(name, options = {}, &block)
69
+ self.t_macros[name] = { :evaluator => block }.merge(options)
70
+ end
71
+
72
+ # Defines a preset of values
73
+ def preset(name, options = {}, &block)
74
+ self.t_presets[name] = { :evaluator => block }.merge(options)
75
+ end
76
+
77
+ # Defines defaults
78
+ def default(preset_name = nil, options = {}, &block)
79
+ self.t_default = { :preset_name => preset_name, :evaluator => block }.merge(options)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,3 @@
1
+ module Olfactory
2
+ VERSION = '0.0.1'
3
+ end
data/lib/olfactory.rb ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'olfactory/template_definition'
3
+ require 'olfactory/template'
4
+
5
+ module Olfactory
6
+ @@templates = {}
7
+ def self.templates
8
+ @@templates
9
+ end
10
+
11
+ def self.template(name, &block)
12
+ new_template_definition = TemplateDefinition.new
13
+ block.call(new_template_definition)
14
+ self.templates[name] = new_template_definition
15
+ end
16
+
17
+ def self.build_template(name, options = {}, &block)
18
+ self.templates[name].build(block, options)
19
+ end
20
+
21
+ def self.reload
22
+ @@templates = {}
23
+ end
24
+ end
data/olfactory.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ $LOAD_PATH << File.expand_path("../lib", __FILE__)
2
+ require 'olfactory/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'olfactory'
6
+ s.version = Olfactory::VERSION
7
+ s.summary = "Olfactory is an extension for factory gems, which adds templates."
8
+ s.description = "Olfactory is an extension for factory gems, which adds templates that allow for easy creation of groups of objects."
9
+ s.authors = ["David Elner"]
10
+ s.email = 'david@davidelner.com'
11
+ s.files = `git ls-files`.split("\n")
12
+ s.test_files = `git ls-files -- {spec,features,gemfiles}/*`.split("\n")
13
+ s.homepage = 'https://github.com/StreetEasy/olfactory'
14
+ s.license = 'MIT'
15
+
16
+ s.require_paths = ['lib']
17
+ s.required_ruby_version = Gem::Requirement.new(">= 1.9.2")
18
+
19
+ s.add_development_dependency("rspec", "~> 1.3.2")
20
+ s.add_development_dependency("pry")
21
+ s.add_development_dependency("pry-nav")
22
+ s.add_development_dependency("pry-stack_explorer")
23
+ end
File without changes
@@ -0,0 +1,16 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
2
+ $LOAD_PATH << File.join(File.dirname(__FILE__))
3
+
4
+ require 'rubygems'
5
+ require 'pry'
6
+ require 'spec/autorun'
7
+
8
+ require 'olfactory'
9
+
10
+ Dir["spec/support/**/*.rb"].each { |f| require File.expand_path(f) }
11
+
12
+ Spec::Runner.configure do |config|
13
+ config.after do
14
+ Olfactory.reload
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: olfactory
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Elner
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-12-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.3.2
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.3.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: pry
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: pry-nav
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: pry-stack_explorer
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Olfactory is an extension for factory gems, which adds templates that
79
+ allow for easy creation of groups of objects.
80
+ email: david@davidelner.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - Gemfile
87
+ - LICENSE
88
+ - README.md
89
+ - lib/olfactory.rb
90
+ - lib/olfactory/template.rb
91
+ - lib/olfactory/template_definition.rb
92
+ - lib/olfactory/version.rb
93
+ - olfactory.gemspec
94
+ - spec/olfactory/template_definition_spec.rb
95
+ - spec/spec_helper.rb
96
+ homepage: https://github.com/StreetEasy/olfactory
97
+ licenses:
98
+ - MIT
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: 1.9.2
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 1.8.25
118
+ signing_key:
119
+ specification_version: 3
120
+ summary: Olfactory is an extension for factory gems, which adds templates.
121
+ test_files:
122
+ - spec/olfactory/template_definition_spec.rb
123
+ - spec/spec_helper.rb