crowdfund_peterpiper 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +8 -0
- data/README +12 -0
- data/bin/crowdfund +30 -0
- data/bin/my_projects.csv +3 -0
- data/bin/projects.csv +3 -0
- data/lib/crowdfund/coin.rb +18 -0
- data/lib/crowdfund/fund_request.rb +81 -0
- data/lib/crowdfund/fundable.rb +28 -0
- data/lib/crowdfund/funding_round.rb +21 -0
- data/lib/crowdfund/grant_project.rb +9 -0
- data/lib/crowdfund/iterators.rb +32 -0
- data/lib/crowdfund/pledge_pool.rb +15 -0
- data/lib/crowdfund/pledgeable.rb +23 -0
- data/lib/crowdfund/project.rb +53 -0
- data/lib/crowdfund/sponsored_project.rb +40 -0
- data/spec/crowdfund/fund_request_spec.rb +56 -0
- data/spec/crowdfund/grant_project_spec.rb +19 -0
- data/spec/crowdfund/pledge_pool_spec.rb +41 -0
- data/spec/crowdfund/project_spec.rb +155 -0
- data/spec/crowdfund/sponsored_project_spec.rb +80 -0
- metadata +108 -0
data/LICENSE
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
Copyright (C) 2013 Peter Sumsion
|
2
|
+
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
5
|
+
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
7
|
+
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
This was my bonus project for the Ruby course at pragmaticstudio.com.
|
2
|
+
The program tracks projects that have funds randomly added or removed.
|
3
|
+
The randomness is driven by Coin class since funding is either added
|
4
|
+
or removed from the project during each funding round.
|
5
|
+
The program runs with either a default projects file, or one can be
|
6
|
+
specified at the command line as a file with
|
7
|
+
title,target_funding,initial_funding. See the default projects.csv file
|
8
|
+
for examples.
|
9
|
+
|
10
|
+
Enjoy!
|
11
|
+
|
12
|
+
PeterPiper
|
data/bin/crowdfund
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/crowdfund/project'
|
4
|
+
require_relative '../lib/crowdfund/fund_request'
|
5
|
+
|
6
|
+
project1 = CrowdFund::Project.new("LMN", 3000, 500)
|
7
|
+
project2 = CrowdFund::Project.new("XYZ", 75, 25)
|
8
|
+
|
9
|
+
fund_request = CrowdFund::FundRequest.new("VC-Friendly Start-up Projects")
|
10
|
+
default_projects_file = File.join(File.dirname(__FILE__), "projects.csv")
|
11
|
+
fund_request.load_projects(ARGV.shift || default_projects_file)
|
12
|
+
|
13
|
+
loop do
|
14
|
+
puts "\nPlease enter the number of rounds: ('quit' to exit)"
|
15
|
+
rounds = gets.chomp.downcase
|
16
|
+
|
17
|
+
case rounds
|
18
|
+
when /^\d+$/
|
19
|
+
fund_request.process_funding(Integer(rounds))
|
20
|
+
when "quit", "exit"
|
21
|
+
fund_request.print_stats
|
22
|
+
thank_you = "Thank YOU for your support!"
|
23
|
+
puts thank_you.center((thank_you.length + 3), "\n")
|
24
|
+
break
|
25
|
+
else
|
26
|
+
puts "\nSorry, we didn't recognize the input: '#{rounds}'"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
fund_request.save_project_funding_status
|
data/bin/my_projects.csv
ADDED
data/bin/projects.csv
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative 'project'
|
2
|
+
require_relative 'coin'
|
3
|
+
require_relative 'funding_round'
|
4
|
+
require 'csv'
|
5
|
+
|
6
|
+
module CrowdFund
|
7
|
+
class FundRequest
|
8
|
+
def initialize(title)
|
9
|
+
@title = title
|
10
|
+
@projects = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_project(project)
|
14
|
+
@projects.push(project)
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_projects(from_file)
|
18
|
+
CSV.foreach(from_file) do |row|
|
19
|
+
name, target, funding = row[0], Integer(row[1]), Integer(row[2])
|
20
|
+
project = Project.new(name, target, funding)
|
21
|
+
add_project(project)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_funding(rounds)
|
26
|
+
1.upto(rounds) do |round|
|
27
|
+
puts "\nProcessing round #{round} of funding for #{@title}."
|
28
|
+
@projects.each do |project|
|
29
|
+
FundingRound.fund_project(project)
|
30
|
+
puts project
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def under_funded_projects
|
36
|
+
@projects.reject { |project| project.fully_funded? }
|
37
|
+
end
|
38
|
+
|
39
|
+
def fully_funded_projects
|
40
|
+
@projects.select { |project| project.fully_funded? }
|
41
|
+
end
|
42
|
+
|
43
|
+
def save_project_funding_status
|
44
|
+
File.open("project_status.txt", "wb") do |file|
|
45
|
+
@projects.each { |project| file.puts project.status }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def print_project_status(project)
|
50
|
+
puts project.status
|
51
|
+
end
|
52
|
+
|
53
|
+
def print_stats
|
54
|
+
puts "\nFunding Results:"
|
55
|
+
puts "\n#{fully_funded_projects.size} Fully-funded Project(s):"
|
56
|
+
fully_funded_projects.each do |project|
|
57
|
+
print_project_status(project)
|
58
|
+
end
|
59
|
+
puts "\n#{under_funded_projects.size} Under-funded Project(s):"
|
60
|
+
under_funded_projects.each do |project|
|
61
|
+
print_project_status(project)
|
62
|
+
end
|
63
|
+
|
64
|
+
puts "\nProjects still needing funding:"
|
65
|
+
under_funded_projects.sort.each do |project|
|
66
|
+
puts "#{project.name.ljust(20, '.')} Funding: $#{project.funding}, Target: $#{project.target}"
|
67
|
+
end
|
68
|
+
|
69
|
+
puts "\nTotal Funds Outstanding:"
|
70
|
+
puts @projects.reduce(0) { |sum, project| sum + project.outstanding_funding }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
if __FILE__ == $0
|
76
|
+
project = Project.new("ABC", 60, 30)
|
77
|
+
fund_request = FundRequest.new("New Fund Group")
|
78
|
+
fund_request.add_project(project)
|
79
|
+
fund_request.process_funding(3)
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CrowdFund
|
2
|
+
module Fundable
|
3
|
+
|
4
|
+
def add_funds(amount=25)
|
5
|
+
puts "Project #{name} got more funds!"
|
6
|
+
@funding += amount
|
7
|
+
end
|
8
|
+
|
9
|
+
def remove_funds(amount=15)
|
10
|
+
if !fully_funded?
|
11
|
+
puts "Project #{name} lost some funds!"
|
12
|
+
if funding < amount
|
13
|
+
self.funding = 0
|
14
|
+
else
|
15
|
+
self.funding -= amount
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def fully_funded?
|
21
|
+
total_funds >= target
|
22
|
+
end
|
23
|
+
|
24
|
+
def <=>(other)
|
25
|
+
outstanding_funding <=> other.outstanding_funding
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'coin'
|
2
|
+
require_relative 'project'
|
3
|
+
require_relative 'pledge_pool'
|
4
|
+
|
5
|
+
module CrowdFund
|
6
|
+
module FundingRound
|
7
|
+
def self.fund_project(project)
|
8
|
+
coin = Coin.new
|
9
|
+
flip_result = coin.flip
|
10
|
+
|
11
|
+
if flip_result == 'heads'
|
12
|
+
project.add_funds
|
13
|
+
elsif flip_result == 'tails'
|
14
|
+
project.remove_funds
|
15
|
+
end
|
16
|
+
|
17
|
+
pledge = PledgePool.random
|
18
|
+
project.make_pledge(pledge)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
def conversation
|
2
|
+
puts "Hello"
|
3
|
+
yield
|
4
|
+
puts "Goodbye"
|
5
|
+
end
|
6
|
+
|
7
|
+
conversation { puts "Good to meet you!" }
|
8
|
+
conversation { puts "My name is Peter."}
|
9
|
+
|
10
|
+
def five_times
|
11
|
+
1.upto(5) do |n|
|
12
|
+
yield(n)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
five_times do |n|
|
17
|
+
puts "#{n} situps"
|
18
|
+
puts "#{n} pushups"
|
19
|
+
puts "#{n} chinups"
|
20
|
+
end
|
21
|
+
|
22
|
+
def n_times(number)
|
23
|
+
1.upto number do |n|
|
24
|
+
yield n
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
n_times(10) do |n|
|
29
|
+
puts "#{n} situps"
|
30
|
+
puts "#{n} pushups"
|
31
|
+
puts "#{n} chinups"
|
32
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module CrowdFund
|
2
|
+
module Pledgeable
|
3
|
+
def make_pledge(pledge)
|
4
|
+
self.pledges[pledge.name] += pledge.amount
|
5
|
+
puts "Project #{name} received a #{pledge.name} pledge worth $#{pledge.amount}"
|
6
|
+
puts "Project #{name}'s pledges: #{pledges}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def each_pledge
|
10
|
+
pledges.each do |name, amount|
|
11
|
+
yield Pledge.new(name, amount)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def pledged_funds
|
16
|
+
pledges.values.reduce(0) { |sum, amount| sum + amount }
|
17
|
+
end
|
18
|
+
|
19
|
+
def total_funds
|
20
|
+
funding + pledged_funds
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative 'pledge_pool'
|
2
|
+
require_relative 'fundable'
|
3
|
+
require_relative 'pledgeable'
|
4
|
+
|
5
|
+
module CrowdFund
|
6
|
+
class Project
|
7
|
+
include Fundable
|
8
|
+
include Pledgeable
|
9
|
+
|
10
|
+
attr_accessor :name, :target, :funding, :pledges
|
11
|
+
|
12
|
+
def initialize(name, target, funding=0)
|
13
|
+
@name = name
|
14
|
+
@funding = funding
|
15
|
+
@target = target
|
16
|
+
@pledges = Hash.new(0)
|
17
|
+
end
|
18
|
+
|
19
|
+
def outstanding_funding
|
20
|
+
if fully_funded?
|
21
|
+
0
|
22
|
+
else
|
23
|
+
@target - total_funds
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def status
|
28
|
+
status = "\n#{@name} funded including pledges at $#{total_funds} for $#{@target} target"
|
29
|
+
status += "\nProject #{@name}'s pledges:"
|
30
|
+
each_pledge do |pledge|
|
31
|
+
status += "\n$#{pledge.amount} in #{pledge.name} pledges"
|
32
|
+
end
|
33
|
+
status += "\n$#{pledged_funds} in total pledges"
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"Project #{@name} has $#{@funding} in funding towards a goal of $#{@target}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if __FILE__ == $0
|
43
|
+
project = Project.new("XYZ", 30, 60)
|
44
|
+
puts project.name
|
45
|
+
puts project.funding
|
46
|
+
puts project.target
|
47
|
+
project.add_funding
|
48
|
+
puts project.funding
|
49
|
+
project.remove_funds
|
50
|
+
puts project.funding
|
51
|
+
puts project.fully_funded?
|
52
|
+
puts project.still_needed
|
53
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative 'project'
|
2
|
+
|
3
|
+
module CrowdFund
|
4
|
+
class SponsoredProject < Project
|
5
|
+
|
6
|
+
attr_reader :unsponsored_max
|
7
|
+
|
8
|
+
def initialize(title, target, funding=0, unsponsored_max=target/2)
|
9
|
+
super(title, target, funding)
|
10
|
+
@unsponsored_max = unsponsored_max
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_funds(amount=25)
|
14
|
+
super(matched_amount(amount))
|
15
|
+
end
|
16
|
+
|
17
|
+
def matched_amount(amount)
|
18
|
+
projected_funding = @funding + amount
|
19
|
+
|
20
|
+
if projected_funding >= @unsponsored_max
|
21
|
+
unsponsored_amount = @unsponsored_max - @funding
|
22
|
+
sponsored_amount = (amount - unsponsored_amount) * 2
|
23
|
+
amount = unsponsored_amount + sponsored_amount
|
24
|
+
end
|
25
|
+
amount
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if __FILE__ == $0
|
31
|
+
target = 500
|
32
|
+
initial_funding = 250
|
33
|
+
unsponsored_max = 260
|
34
|
+
project = SponsoredProject.new("Almost There", target, initial_funding, unsponsored_max)
|
35
|
+
|
36
|
+
|
37
|
+
project.add_funds
|
38
|
+
|
39
|
+
puts project.funding
|
40
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'crowdfund/project'
|
2
|
+
require 'crowdfund/fund_request'
|
3
|
+
require 'crowdfund/coin'
|
4
|
+
require 'crowdfund/funding_round'
|
5
|
+
require 'csv'
|
6
|
+
|
7
|
+
module CrowdFund
|
8
|
+
describe FundRequest do
|
9
|
+
|
10
|
+
before do
|
11
|
+
$stdout = StringIO.new
|
12
|
+
@initial_funding = 30
|
13
|
+
@target = 4000
|
14
|
+
@project = Project.new("ABC Project", @target, @initial_funding)
|
15
|
+
@fund_request = FundRequest.new("DEF Funding Group")
|
16
|
+
@fund_request.add_project(@project)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "adds funds if heads is flipped" do
|
20
|
+
Coin.any_instance.stub(:flip).and_return('heads')
|
21
|
+
|
22
|
+
@fund_request.process_funding(1)
|
23
|
+
@project.funding.should == (@initial_funding + 25)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "removes funds if tails is flipped" do
|
27
|
+
Coin.any_instance.stub(:flip).and_return('tails')
|
28
|
+
|
29
|
+
@fund_request.process_funding(1)
|
30
|
+
@project.funding.should == (@initial_funding - 15)
|
31
|
+
end
|
32
|
+
|
33
|
+
context "has both under and fully funded projects" do
|
34
|
+
before do
|
35
|
+
@under_funded1 = Project.new("DEF", 1000, 500)
|
36
|
+
@under_funded2 = Project.new("GHI", 75, 74)
|
37
|
+
@fully_funded1 = Project.new("JKL", 100, 100)
|
38
|
+
@fully_funded2 = Project.new("MNO", 500, 600)
|
39
|
+
|
40
|
+
@funding_request = FundRequest.new("Hep Projects")
|
41
|
+
@funding_request.add_project(@under_funded1)
|
42
|
+
@funding_request.add_project(@under_funded2)
|
43
|
+
@funding_request.add_project(@fully_funded1)
|
44
|
+
@funding_request.add_project(@fully_funded2)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns all under funded projects" do
|
48
|
+
@funding_request.under_funded_projects.should == [@under_funded1, @under_funded2]
|
49
|
+
end
|
50
|
+
|
51
|
+
it "returns all fully funded projects" do
|
52
|
+
@funding_request.fully_funded_projects.should == [@fully_funded1, @fully_funded2]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'crowdfund/project'
|
2
|
+
require 'crowdfund/grant_project'
|
3
|
+
|
4
|
+
module CrowdFund
|
5
|
+
describe GrantProject do
|
6
|
+
|
7
|
+
before do
|
8
|
+
@target = 5000
|
9
|
+
@initial_funding = 300
|
10
|
+
@project = GrantProject.new("ABC Grant", @target, @initial_funding)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "doesn't allow funding to be removed" do
|
14
|
+
@project.remove_funds
|
15
|
+
|
16
|
+
@project.funding.should == @initial_funding
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'crowdfund/pledge_pool'
|
2
|
+
|
3
|
+
module CrowdFund
|
4
|
+
describe Pledge do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@pledge = Pledge.new(:silver, 75)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "has a name" do
|
11
|
+
@pledge.name.should == :silver
|
12
|
+
end
|
13
|
+
|
14
|
+
it "has a dollar amount" do
|
15
|
+
@pledge.amount.should == 75
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
describe PledgePool do
|
21
|
+
|
22
|
+
it "has a bronze pledge worth 50 dollars" do
|
23
|
+
PledgePool::PLEDGES[0].should == Pledge.new(:bronze, 50)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "has a silver pledge worth 75 dollars" do
|
27
|
+
PledgePool::PLEDGES[1].should == Pledge.new(:silver, 75)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "has a gold pledge worth 100 dollars" do
|
31
|
+
PledgePool::PLEDGES[2].should == Pledge.new(:gold, 100)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "returns a random pledge" do
|
35
|
+
pledge = PledgePool::random
|
36
|
+
|
37
|
+
PledgePool::PLEDGES.should include(pledge)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'crowdfund/project'
|
2
|
+
require 'crowdfund/fund_request'
|
3
|
+
require 'crowdfund/pledge_pool'
|
4
|
+
|
5
|
+
module CrowdFund
|
6
|
+
describe Project do
|
7
|
+
|
8
|
+
before do
|
9
|
+
$stdout = StringIO.new
|
10
|
+
@initial_funding = 300
|
11
|
+
@target = 1000
|
12
|
+
@project = Project.new("XYZ", @target, @initial_funding)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "has an initial target initial_funding amount" do
|
16
|
+
@project.target.should == 1000
|
17
|
+
end
|
18
|
+
|
19
|
+
it "computes the total funding outstanding as the target funding amount minus the total funds including pledges" do
|
20
|
+
@project.outstanding_funding.should == (@target - @project.total_funds)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "increases funds by 25 when funds are added" do
|
24
|
+
@project.add_funds
|
25
|
+
@project.funding.should == (@initial_funding + 25)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "decreases funds by 15 when funds are removed" do
|
29
|
+
@project.remove_funds
|
30
|
+
@project.funding.should == (@initial_funding - 15)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
it "stores a pledge after each funding round" do
|
35
|
+
fund_request = FundRequest.new("Some project group")
|
36
|
+
project = Project.new("ABC", 4000)
|
37
|
+
|
38
|
+
fund_request.add_project(project)
|
39
|
+
|
40
|
+
project.make_pledge(Pledge.new(:bronze, 50))
|
41
|
+
|
42
|
+
project.pledged_funds.should == 50
|
43
|
+
end
|
44
|
+
|
45
|
+
it "calculates total funding as the sum of initial funding, added funds, and added pledges" do
|
46
|
+
initial_funding = 100
|
47
|
+
pledge_amount = 100
|
48
|
+
pledge = Pledge.new(:gold, pledge_amount)
|
49
|
+
project = Project.new("ABC", 1000, initial_funding)
|
50
|
+
project.add_funds
|
51
|
+
project.make_pledge(pledge)
|
52
|
+
|
53
|
+
project.total_funds.should == (initial_funding + pledge_amount + 25)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "yields each pledge and it's amount" do
|
57
|
+
@project.make_pledge(Pledge.new(:gold, 100))
|
58
|
+
@project.make_pledge(Pledge.new(:gold, 100))
|
59
|
+
@project.make_pledge(Pledge.new(:gold, 100))
|
60
|
+
@project.make_pledge(Pledge.new(:gold, 100))
|
61
|
+
@project.make_pledge(Pledge.new(:silver, 75))
|
62
|
+
@project.make_pledge(Pledge.new(:bronze, 50))
|
63
|
+
|
64
|
+
yielded = []
|
65
|
+
|
66
|
+
@project.each_pledge do |pledge|
|
67
|
+
yielded << pledge
|
68
|
+
end
|
69
|
+
|
70
|
+
yielded.should == [
|
71
|
+
Pledge.new(:gold, 400),
|
72
|
+
Pledge.new(:silver, 75),
|
73
|
+
Pledge.new(:bronze, 50)
|
74
|
+
]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "represents status including pledges as a string" do
|
78
|
+
status = "\n#{@project.name} funded including pledges at $#{@project.total_funds} for $#{@project.target} target"
|
79
|
+
status += "\nProject #{@project.name}'s pledges:"
|
80
|
+
@project.each_pledge do |pledge|
|
81
|
+
status += "\n$#{pledge.amount} in #{pledge.name} pledges"
|
82
|
+
end
|
83
|
+
status += "\n$#{@project.pledged_funds} in total pledges"
|
84
|
+
|
85
|
+
@project.status.should == status
|
86
|
+
end
|
87
|
+
|
88
|
+
context "when funding is less than target but pledges would fully fund it" do
|
89
|
+
before do
|
90
|
+
@initial_funding = 100
|
91
|
+
@target = 110
|
92
|
+
@pledge_amount = 50
|
93
|
+
@project = Project.new("Meet the Muppets", @target, @initial_funding)
|
94
|
+
@project.make_pledge(Pledge.new(:bronze, @pledge_amount))
|
95
|
+
end
|
96
|
+
|
97
|
+
it "is fully funded" do
|
98
|
+
@project.should be_fully_funded
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "when funding is greater than or equal to target" do
|
103
|
+
before do
|
104
|
+
@initial_funding = 2005
|
105
|
+
@target = 1995
|
106
|
+
@project = Project.new("XYZ", @target, @initial_funding)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "is fully funded" do
|
110
|
+
@project.should be_fully_funded
|
111
|
+
end
|
112
|
+
|
113
|
+
it "request to remove funds skips this project" do
|
114
|
+
@project.remove_funds
|
115
|
+
|
116
|
+
@project.funding.should == @initial_funding
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when funding is less than or equal to target" do
|
121
|
+
before do
|
122
|
+
@initial_funding = 200
|
123
|
+
@target = 300
|
124
|
+
@project = Project.new("Project 1", @target, @initial_funding)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "is under funded" do
|
128
|
+
@project.should_not be_fully_funded
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
context "no initial funding provided" do
|
134
|
+
before do
|
135
|
+
@project = Project.new("ABC", 3000)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "has a default value of 0 for funding amount" do
|
139
|
+
@project.funding.should == 0
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context "removing funding would take fund below zero" do
|
144
|
+
before do
|
145
|
+
@project = Project.new("DEF", 500, 10)
|
146
|
+
end
|
147
|
+
|
148
|
+
it "takes funding to zero" do
|
149
|
+
@project.remove_funds
|
150
|
+
@project.funding.should == 0
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'crowdfund/project'
|
2
|
+
require 'crowdfund/sponsored_project'
|
3
|
+
|
4
|
+
module CrowdFund
|
5
|
+
describe SponsoredProject do
|
6
|
+
|
7
|
+
before do
|
8
|
+
@target = 10000
|
9
|
+
@initial_funding = 100
|
10
|
+
@project = SponsoredProject.new("Wells for Africa", @target, @initial_funding)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has a default max amount of funding needed for additional funds to be matched" do
|
14
|
+
@project.unsponsored_max.should == @target / 2
|
15
|
+
end
|
16
|
+
|
17
|
+
context "unsponsored max has not been met" do
|
18
|
+
|
19
|
+
before do
|
20
|
+
@target = 5000
|
21
|
+
@initial_funding = 50
|
22
|
+
@unsponsored_max = 3000
|
23
|
+
@project = SponsoredProject.new("Unstable Startup Charity", @target, @initial_funding, @unsponsored_max)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns same amount passed in as matched amount" do
|
27
|
+
@project.matched_amount(200).should == 200
|
28
|
+
end
|
29
|
+
|
30
|
+
it "doesn't match added funds" do
|
31
|
+
@project.add_funds
|
32
|
+
|
33
|
+
@project.funding.should == @initial_funding + 25
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
context "unsponsored max has been met" do
|
39
|
+
|
40
|
+
before do
|
41
|
+
@target = 8000
|
42
|
+
@initial_funding = 200
|
43
|
+
@unsponsored_max = 200
|
44
|
+
@project = SponsoredProject.new("Health for Humanity", @target, @initial_funding, @unsponsored_max)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns double the amount as matched amount" do
|
48
|
+
@project.matched_amount(250).should == 250 * 2
|
49
|
+
@project.matched_amount(130).should == 130 * 2
|
50
|
+
end
|
51
|
+
|
52
|
+
it "matches added funds" do
|
53
|
+
@project.add_funds
|
54
|
+
|
55
|
+
@project.funding.should == @initial_funding + 50
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "next funds added will make funds exceed unsponsored max" do
|
60
|
+
|
61
|
+
before do
|
62
|
+
@target = 500
|
63
|
+
@initial_funding = 250
|
64
|
+
@unsponsored_max = 260
|
65
|
+
@project = SponsoredProject.new("Almost There", @target, @initial_funding, @unsponsored_max)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns sum of sponsored and unsponsored funds as matched amount" do
|
69
|
+
@project.matched_amount(300).should == 10 + (290 * 2)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "matches any and all funds over unsponsored max" do
|
73
|
+
@project.add_funds
|
74
|
+
|
75
|
+
@project.funding.should == @initial_funding + 10 + (15 * 2)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: crowdfund_peterpiper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Peter Sumsion
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-11-17 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: '0'
|
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: '0'
|
30
|
+
description: ! 'This was my bonus project for the Ruby course at pragmaticstudio.com.
|
31
|
+
|
32
|
+
The program tracks projects that have funds randomly added or removed.
|
33
|
+
|
34
|
+
The randomness is driven by Coin class since funding is either added
|
35
|
+
|
36
|
+
or removed from the project during each funding round.
|
37
|
+
|
38
|
+
The program runs with either a default projects file, or one can be
|
39
|
+
|
40
|
+
specified at the command line as a file with
|
41
|
+
|
42
|
+
title,target_funding,initial_funding. See the default projects.csv file
|
43
|
+
|
44
|
+
for examples.
|
45
|
+
|
46
|
+
|
47
|
+
Enjoy!
|
48
|
+
|
49
|
+
|
50
|
+
PeterPiper
|
51
|
+
|
52
|
+
'
|
53
|
+
email: sumsionp@gmail.com
|
54
|
+
executables:
|
55
|
+
- crowdfund
|
56
|
+
extensions: []
|
57
|
+
extra_rdoc_files: []
|
58
|
+
files:
|
59
|
+
- bin/crowdfund
|
60
|
+
- bin/my_projects.csv
|
61
|
+
- bin/projects.csv
|
62
|
+
- lib/crowdfund/coin.rb
|
63
|
+
- lib/crowdfund/fund_request.rb
|
64
|
+
- lib/crowdfund/fundable.rb
|
65
|
+
- lib/crowdfund/funding_round.rb
|
66
|
+
- lib/crowdfund/grant_project.rb
|
67
|
+
- lib/crowdfund/iterators.rb
|
68
|
+
- lib/crowdfund/pledge_pool.rb
|
69
|
+
- lib/crowdfund/pledgeable.rb
|
70
|
+
- lib/crowdfund/project.rb
|
71
|
+
- lib/crowdfund/sponsored_project.rb
|
72
|
+
- spec/crowdfund/fund_request_spec.rb
|
73
|
+
- spec/crowdfund/grant_project_spec.rb
|
74
|
+
- spec/crowdfund/pledge_pool_spec.rb
|
75
|
+
- spec/crowdfund/project_spec.rb
|
76
|
+
- spec/crowdfund/sponsored_project_spec.rb
|
77
|
+
- LICENSE
|
78
|
+
- README
|
79
|
+
homepage: http://psumsiontech.wordpress.com/2013/11/17/crowd-funding-ruby-gem/
|
80
|
+
licenses: []
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '1.9'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.8.25
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: Multiple project random crowd funding program
|
103
|
+
test_files:
|
104
|
+
- spec/crowdfund/fund_request_spec.rb
|
105
|
+
- spec/crowdfund/grant_project_spec.rb
|
106
|
+
- spec/crowdfund/pledge_pool_spec.rb
|
107
|
+
- spec/crowdfund/project_spec.rb
|
108
|
+
- spec/crowdfund/sponsored_project_spec.rb
|