motivation 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,18 @@
1
+ *.gem
2
+ *.rbc
3
+ *.rvmrc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in motivation.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Brendon Murphy
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Motivation
2
+
3
+ Simple DSL for use in classes to motivate a user towards a goal. An example
4
+ goal might be "Complete Profile", or "Setup Project"
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'motivation'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install motivation
19
+
20
+ ## Usage
21
+
22
+ ```ruby
23
+ require "motivation"
24
+
25
+ Profile = Struct.new(:twitter_name, :age, :tweets) do
26
+ attr_accessor :cached_tweets_count
27
+ end
28
+
29
+ class ProfileMotivation
30
+ include Motivation
31
+
32
+ # Aliases profile to subject for clarity in steps
33
+ subject :profile
34
+
35
+ # Create a simple check. This defines the predicate `#setup_twitter?`
36
+ check(:setup_twitter) { profile.twitter_name.to_s.length > 0 }
37
+
38
+ # Define a step by name then create a check
39
+ step :enter_age
40
+ check { profile.age.to_i > 0 }
41
+
42
+ # Define a completetion block. This is useful if you your
43
+ # check is heavy and you want to use a cached result. This
44
+ # will define a method `#complete_tweets_added
45
+ step :tweets_added
46
+ check { cached_tweets_count.to_i > 0 || profile.tweets.length > 0 }
47
+ complete { profile.cached_tweets_count = profile.tweets.length.to_i }
48
+ end
49
+
50
+ profile = Profile.new(nil, 42, [1,2,3])
51
+ motivation = ProfileMotivation.new(profile)
52
+ motivation.setup_twitter? #=> false
53
+ profile.twitter_name = "JohnDoe"
54
+ motivation.setup_twitter? #=> true
55
+
56
+ motivation.complete_tweets_added
57
+ ```
58
+
59
+ Note that the `check` and `complete` blocks will not tolerate early returns, you
60
+ will get a `LocalJumpError`. If you want to simplify your DSL definitions, you can
61
+ just call methods, including privates, in your ProfileMotivation class itself.
62
+
63
+ ## Acknowledgements
64
+
65
+ This was heavily inspired by [progression](https://github.com/mguterl/progression).
66
+
67
+ I switched primarily because I wanted to use specific classes rather than inject into
68
+ an existing model namespace, and extend the DSL some.
69
+
70
+ ## Contributing
71
+
72
+ 1. Fork it
73
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
74
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
75
+ 4. Push to the branch (`git push origin my-new-feature`)
76
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler/gem_tasks"
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc "Run all examples"
7
+ RSpec::Core::RakeTask.new(:spec) do |t|
8
+ t.rspec_opts = %w[--color]
9
+ end
10
+
@@ -0,0 +1,3 @@
1
+ module Motivation
2
+ VERSION = "0.0.1"
3
+ end
data/lib/motivation.rb ADDED
@@ -0,0 +1,170 @@
1
+ require "motivation/version"
2
+ require "delegate"
3
+
4
+ module Motivation
5
+
6
+ def self.included(base)
7
+ base.send(:attr_accessor, :subject)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ def initialize(subject)
12
+ @subject = subject
13
+ end
14
+
15
+ def applies?
16
+ true
17
+ end
18
+
19
+ def checks
20
+ self.class.checks
21
+ end
22
+
23
+ def each_check(&block)
24
+ wrapped_checks.each &block
25
+ end
26
+
27
+ def wrapped_checks
28
+ checks.collect { |c| WrappedCheck.new(c, self) }
29
+ end
30
+
31
+ def completions
32
+ self.class.completions
33
+ end
34
+
35
+ def translation_key
36
+ self.class.translation_key
37
+ end
38
+
39
+ module ClassMethods
40
+ def checks
41
+ @checks ||= []
42
+ end
43
+
44
+ def completions
45
+ @completions ||= []
46
+ end
47
+
48
+ ##
49
+ # Returns the underscored name used in the full i18n translation key
50
+ #
51
+ # Example:
52
+ #
53
+ # UserProjectMotivation.translation_key # => "user_project"
54
+ #
55
+ def translation_key
56
+ key = name.gsub(/Motivation\z/, '')
57
+ key.gsub!(/^.*::/, '')
58
+ key.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
59
+ key.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
60
+ key.tr!("-", "_")
61
+ key.downcase!
62
+ end
63
+
64
+ def subject(attr_name)
65
+ alias_method attr_name.to_sym, :subject
66
+ end
67
+
68
+ ##
69
+ # Grant the ability to define if a progression applies. This
70
+ # enables creation a progression that might only apply if a project
71
+ # is less than 30 days old
72
+ def applies(&block)
73
+ define_method("applies?") do
74
+ !! instance_exec(&block)
75
+ end
76
+ end
77
+
78
+ ##
79
+ # Define the current step name, used by subsequent
80
+ # DSL calls
81
+ def step(name)
82
+ @current_step_name = name
83
+ end
84
+
85
+ ##
86
+ # Create a predicate method for the current step name
87
+ # to test if it's complete
88
+ def check(name = @current_step_name, &block)
89
+ raise "No step name" unless name
90
+ @current_step_name ||= name
91
+ checks << CheckBlock.new("progression_name", name, &block)
92
+
93
+ define_method("#{name}?") do
94
+ check = checks.find { |s| s.name == name }
95
+ !! check.run(self)
96
+ end
97
+ end
98
+
99
+ # Check a method like `complete_current_step_name` to mark
100
+ # a step complete. This is not always needed but useful
101
+ # if you want to persist a completion for performance purposes.
102
+ def complete(&block)
103
+ name = @current_step_name or raise "No step name"
104
+ completions << CompletionBlock.new("progression_name", name, &block)
105
+
106
+ define_method("complete_#{name}") do
107
+ completion = completions.find { |c| c.name == name }
108
+ completion.run(self)
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ class StepBlock
115
+ attr_reader :name
116
+
117
+ def initialize(progression_name, name, &block)
118
+ @progression_name = progression_name
119
+ @name = name
120
+ @block = block
121
+ end
122
+
123
+ def run(context)
124
+ context.instance_exec &@block
125
+ end
126
+ end
127
+
128
+ class CheckBlock < StepBlock; end
129
+ class CompletionBlock < StepBlock; end
130
+
131
+ ##
132
+ # WrappedCheck is used to wrap a Check with the context
133
+ # of a motivation instance, so that you can check if
134
+ # it is completed without passing in the subject instance
135
+ #
136
+ class WrappedCheck < DelegateClass(CheckBlock)
137
+ def initialize(check, motivation)
138
+ super(check)
139
+ @check = check
140
+ @motivation = motivation
141
+ end
142
+
143
+ def completed?
144
+ !! @check.run(@motivation)
145
+ end
146
+
147
+ ##
148
+ # Returns a key for i18n like:
149
+ #
150
+ # 'motivations.project.incomplete.step_name'
151
+ #
152
+ # or for a completed step
153
+ # 'motivations.project.complete.signed_up'
154
+ #
155
+ def translation_key
156
+ [
157
+ "motivations",
158
+ @motivation.key,
159
+ status_key,
160
+ @check.name
161
+ ].join('.')
162
+ end
163
+ private
164
+
165
+ def status_key
166
+ completed? ? "complete" : "incomplete"
167
+ end
168
+ end
169
+
170
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/motivation/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Brendon Murphy"]
6
+ gem.email = ["xternal1+github@gmail.com"]
7
+ gem.summary = %q{Simple DSL for use in classes to motivate a user towards a goal}
8
+ gem.description = gem.summary
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "motivation"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Motivation::VERSION
17
+
18
+ gem.add_development_dependency "rspec", "~> 2.13.0"
19
+ end
@@ -0,0 +1,171 @@
1
+ require 'motivation'
2
+ require "ostruct"
3
+
4
+ Project = Struct.new(:name, :subdomain, :users) do
5
+ attr_accessor :users_count
6
+ end
7
+
8
+ class ProjectMotivation
9
+ include Motivation
10
+
11
+ attr_accessor :applies, :method_check
12
+ # Privatize just the getter so we
13
+ # cam make sure everything is callable
14
+ private :method_check
15
+
16
+ applies { !! applies }
17
+
18
+ subject :project
19
+
20
+ check(:name_setup) { ! project.name.to_s.empty? }
21
+
22
+ step :subdomain_setup
23
+ check { ! project.subdomain.to_s.empty? }
24
+
25
+ check(:local_method_step) { method_check }
26
+
27
+ step :users_signed_up
28
+ check do
29
+ project.users_count.to_i > 0 || project.users.length > 0
30
+ end
31
+ complete {
32
+ project.users_count = project.users.length
33
+ }
34
+ end
35
+
36
+ describe Motivation do
37
+ let(:project) { Project.new("Spec Project", "spec-subdomain", [stub, stub]) }
38
+ let(:motivation) { ProjectMotivation.new project }
39
+
40
+ it "provides a subject class method for aliasing the subject" do
41
+ expect(motivation.project).to_not be_nil
42
+ expect(motivation.project).to eq motivation.subject
43
+ end
44
+
45
+ context "a simple check step" do
46
+ it "creates a predicate method provided a name and block" do
47
+ project.name = nil
48
+ expect(motivation).to_not be_name_setup
49
+
50
+ project.name = "Spec Project"
51
+ expect(motivation).to be_name_setup
52
+ end
53
+ end
54
+
55
+ context "a step definition followed by a check" do
56
+ it "creates a predicate method provided a name and block" do
57
+ project.subdomain = nil
58
+ expect(motivation).to_not be_subdomain_setup
59
+
60
+ project.subdomain = "spec-subdomain"
61
+ expect(motivation).to be_subdomain_setup
62
+ end
63
+ end
64
+
65
+ context "a step definition calling progression instance methods" do
66
+ it "can call the instance method with expected scope" do
67
+ expect(motivation).to_not be_local_method_step
68
+ motivation.method_check = true
69
+ expect(motivation).to be_local_method_step
70
+ end
71
+ end
72
+
73
+ context "a step definition followed by a check and a completion" do
74
+ it "creates the completion" do
75
+ project.users = [stub, stub, stub]
76
+ motivation.complete_users_signed_up
77
+ expect(project.users_count).to eq 3
78
+ expect(motivation).to be_users_signed_up
79
+ end
80
+ end
81
+
82
+ describe "application checks" do
83
+ it "are true by default" do
84
+ klass = Class.new do
85
+ include Motivation
86
+ end
87
+
88
+ expect(klass.new(stub).applies?).to be_true
89
+ end
90
+
91
+ it "are overridden with the apply dsl" do
92
+ motivation.applies = nil
93
+ expect(motivation.applies?).to be_false
94
+ end
95
+ end
96
+
97
+ describe ".translation_key" do
98
+ it "project for ProjectMotivation" do
99
+ expect(ProjectMotivation.translation_key).to eq "project"
100
+ end
101
+
102
+ it "user_project for UserProjectMotivation" do
103
+ UserProjectMotivation = Class.new do
104
+ include Motivation
105
+ end
106
+ expect(UserProjectMotivation.translation_key).to eq "user_project"
107
+ end
108
+
109
+ it "is accessable via the instance" do
110
+ expect(motivation.translation_key).to eq "project"
111
+ end
112
+ end
113
+
114
+ describe "iterating checks" do
115
+ it "steps through the checks in order" do
116
+ names = []
117
+
118
+ motivation.each_check do |c|
119
+ names << c.name
120
+ end
121
+
122
+ expect(names).to eq [:name_setup, :subdomain_setup, :local_method_step, :users_signed_up]
123
+ end
124
+
125
+ it "wraps the checks to allow checking if it's completed" do
126
+ project.name = "name"
127
+ project.subdomain = nil
128
+ results = []
129
+
130
+ motivation.each_check do |c|
131
+ results << c.completed?
132
+ end
133
+
134
+ expect(results[0]).to be_true
135
+ expect(results[1]).to be_false
136
+ end
137
+ end
138
+
139
+ end
140
+
141
+ describe Motivation::WrappedCheck, "#translation_key" do
142
+ let(:check) { OpenStruct.new }
143
+ let(:motivation) { stub(:motivation, :key => "project") }
144
+ let(:wrapped_check) { Motivation::WrappedCheck.new(check, motivation) }
145
+
146
+ before do
147
+ class << check
148
+ attr_accessor :flag
149
+
150
+ def run(*)
151
+ !! flag
152
+ end
153
+ end
154
+
155
+ check.name = "foo_bar"
156
+ end
157
+
158
+ context "for a incomplete step" do
159
+ it "generates a key like motivations.project.incomplete.step_name" do
160
+ check.flag = false
161
+ expect(wrapped_check.translation_key).to eq "motivations.project.incomplete.foo_bar"
162
+ end
163
+ end
164
+
165
+ context "for a completed step" do
166
+ it "generates a key like motivations.project.complete.step_name" do
167
+ check.flag = true
168
+ expect(wrapped_check.translation_key).to eq "motivations.project.complete.foo_bar"
169
+ end
170
+ end
171
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: motivation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brendon Murphy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &2160517160 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.13.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2160517160
25
+ description: Simple DSL for use in classes to motivate a user towards a goal
26
+ email:
27
+ - xternal1+github@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - .gitignore
33
+ - Gemfile
34
+ - LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - lib/motivation.rb
38
+ - lib/motivation/version.rb
39
+ - motivation.gemspec
40
+ - spec/motivation_spec.rb
41
+ homepage: ''
42
+ licenses: []
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 1.8.15
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Simple DSL for use in classes to motivate a user towards a goal
65
+ test_files:
66
+ - spec/motivation_spec.rb