omega-tariffs-base 0.1.5
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/CHANGELOG +1 -0
- data/Manifest +30 -0
- data/README.rdoc +10 -0
- data/Rakefile +14 -0
- data/lib/computer_class_filter.rb +37 -0
- data/lib/deny_filter.rb +24 -0
- data/lib/filter.rb +43 -0
- data/lib/omega-tariffs-base.rb +13 -0
- data/lib/packet_filter.rb +111 -0
- data/lib/rate_filter.rb +31 -0
- data/lib/regioned_calculation.rb +61 -0
- data/lib/repeating_filter.rb +188 -0
- data/lib/settings.rb +42 -0
- data/lib/single_time_filter.rb +67 -0
- data/lib/tarif_builder.rb +16 -0
- data/lib/weekday_filter.rb +53 -0
- data/omega-tariffs-base.gemspec +36 -0
- data/spec/complex_tarif_spec.rb +54 -0
- data/spec/computer_class_filter_spec.rb +82 -0
- data/spec/deny_filter_spec.rb +23 -0
- data/spec/filter_spec.rb +36 -0
- data/spec/lib/active_record_classes_stub.rb +151 -0
- data/spec/lib/init.rb +5 -0
- data/spec/lib/spec_helper.rb +9 -0
- data/spec/packet_filter_spec.rb +287 -0
- data/spec/rate_filter_spec.rb +36 -0
- data/spec/repeating_filter_spec.rb +250 -0
- data/spec/settings_spec.rb +62 -0
- data/spec/single_time_filter_spec.rb +122 -0
- data/spec/tarif_builder_spec.rb +43 -0
- data/spec/weekday_filter_spec.rb +144 -0
- metadata +147 -0
data/lib/settings.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'activesupport', '=2.3.8'
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
module UniqSysOmega
|
6
|
+
module Tariffs
|
7
|
+
|
8
|
+
class Settings
|
9
|
+
def initialize(hash)
|
10
|
+
@proxy = Proxy.new(hash)
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(meth, *args)
|
14
|
+
@proxy.send(meth, *args)
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
class Proxy
|
19
|
+
def initialize(hash)
|
20
|
+
raise ArgumentError, "Invalid argument for settings. Should be hash." unless hash.kind_of?(Hash)
|
21
|
+
@hash = HashWithIndifferentAccess.new(hash)
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing(meth, *args)
|
25
|
+
entry = @hash[meth]
|
26
|
+
return Proxy.new(entry) if entry.kind_of?(Hash)
|
27
|
+
entry
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_hash
|
31
|
+
@hash.dup
|
32
|
+
end
|
33
|
+
|
34
|
+
def ==(other)
|
35
|
+
raise "Invalid comparison" unless other.kind_of?(Hash) || other.kind_of?(Settings::Proxy) || other.kind_of?(Settings)
|
36
|
+
@hash == HashWithIndifferentAccess.new(other.to_hash)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module UniqSysOmega
|
2
|
+
module Tariffs
|
3
|
+
|
4
|
+
class SingleTimeFilterRepository
|
5
|
+
class <<self
|
6
|
+
def had_single_payment?(tarif_id, login_id, period_id)
|
7
|
+
return false unless @payments
|
8
|
+
@payments.member? [ tarif_id, login_id, period_id ]
|
9
|
+
end
|
10
|
+
|
11
|
+
def register_single_payment(tarif_id, login_id, period_id)
|
12
|
+
@payments ||= [ ]
|
13
|
+
@payments << [ tarif_id, login_id, period_id ] unless had_single_payment?(tarif_id, login_id, period_id)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class SingleTimeFilter < Filter
|
19
|
+
def start_permitted?(computer_id, login_id, options={})
|
20
|
+
single_payment_setup? login_id, options[:period].begin
|
21
|
+
end
|
22
|
+
|
23
|
+
def permitted?(computer_id, login_id, options={})
|
24
|
+
single_payment_setup? login_id, options[:period].begin
|
25
|
+
end
|
26
|
+
|
27
|
+
def calculate_cost(computer_id, login_id, started_at, amount, options={})
|
28
|
+
SingleTimeFilterRepository.had_single_payment?(tarif_id, login_id, options[:period].begin) ?
|
29
|
+
0 : @price
|
30
|
+
end
|
31
|
+
|
32
|
+
def process_activity(unprocessed_activity, ft, options={})
|
33
|
+
if SingleTimeFilterRepository.had_single_payment?(tarif_id, unprocessed_activity.login_id, options[:period].begin)
|
34
|
+
true
|
35
|
+
else
|
36
|
+
make_single_payment(ft, unprocessed_activity.login_id, options)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def single_payment_setup?(login_id, period_id)
|
42
|
+
SingleTimeFilterRepository.had_single_payment?(tarif_id, login_id, period_id) ||
|
43
|
+
can_make_single_payment?(login_id)
|
44
|
+
end
|
45
|
+
|
46
|
+
def can_make_single_payment?(login_id)
|
47
|
+
Login.find(login_id).balance - @price >= 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def make_single_payment(ft, login_id, options)
|
51
|
+
return false unless can_make_single_payment?(login_id)
|
52
|
+
|
53
|
+
# TODO: Make it transactional!
|
54
|
+
SingleTimeFilterRepository.register_single_payment(tarif_id, login_id, options[:period].begin)
|
55
|
+
|
56
|
+
ft = ft.dup
|
57
|
+
ft.volume = @price
|
58
|
+
ft.save
|
59
|
+
end
|
60
|
+
|
61
|
+
def after_initialize
|
62
|
+
@price = settings.price
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module UniqSysOmega
|
2
|
+
module Tariffs
|
3
|
+
|
4
|
+
class TarifBuilder
|
5
|
+
def initialize
|
6
|
+
raise "Cannot instantinate singleton class"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.build(settings, tarif_id)
|
10
|
+
class_name = eval(settings.klass)
|
11
|
+
class_name.new(settings.settings, tarif_id)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module UniqSysOmega
|
2
|
+
module Tariffs
|
3
|
+
|
4
|
+
class WeekdayFilter < Filter
|
5
|
+
def start_permitted?(computer_id, login_id, options={})
|
6
|
+
@weekdays[Time.now.wday].start_permitted?(computer_id, login_id, options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def permitted?(computer_id, login_id, options={})
|
10
|
+
@weekdays[Time.now.wday].permitted?(computer_id, login_id, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def calculate_cost(computer_id, login_id, started_at, amount, options={})
|
14
|
+
cost = 0
|
15
|
+
for_each_weekday(started_at, amount) do |period_start, period_length|
|
16
|
+
cost += @weekdays[period_start.wday].calculate_cost(computer_id, login_id, period_start, period_length, options)
|
17
|
+
end
|
18
|
+
cost
|
19
|
+
end
|
20
|
+
|
21
|
+
def process_activity(unprocessed_activity, ft, options={})
|
22
|
+
for_each_weekday(unprocessed_activity.created_at, unprocessed_activity.amount) do |period_start, period_length|
|
23
|
+
@weekdays[period_start.wday].process_activity(alter_activity_period(unprocessed_activity, period_start, period_length), ft, options) or return false
|
24
|
+
end
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
def for_each_weekday(started_at, amount, *args)
|
30
|
+
day_threshold = started_at.beginning_of_day
|
31
|
+
while true
|
32
|
+
period = (day_threshold .. day_threshold + 1.day) & (started_at .. started_at+amount)
|
33
|
+
break if period.nil?
|
34
|
+
yield period.begin, period.end-period.begin, *args unless period.end-period.begin == 0
|
35
|
+
day_threshold += 1.day
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def after_initialize
|
40
|
+
@weekdays = [nil]*7
|
41
|
+
settings.to_hash.each do |period, settings|
|
42
|
+
filter = TarifBuilder.build Settings.new(settings), tarif_id
|
43
|
+
period.to_a.each do |day|
|
44
|
+
raise "Day already defined. Wrong tarif settings." unless @weekdays[day].nil?
|
45
|
+
@weekdays[day] = filter
|
46
|
+
end
|
47
|
+
end
|
48
|
+
raise "Some days left undefined." unless @weekdays.compact.size == 7
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{omega-tariffs-base}
|
5
|
+
s.version = "0.1.5"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Uniq Systems"]
|
9
|
+
s.date = %q{2010-09-23}
|
10
|
+
s.description = %q{Omega Sector base tariffs}
|
11
|
+
s.email = %q{ivan@uniqsystems.ru}
|
12
|
+
s.extra_rdoc_files = ["CHANGELOG", "README.rdoc", "lib/computer_class_filter.rb", "lib/deny_filter.rb", "lib/filter.rb", "lib/omega-tariffs-base.rb", "lib/packet_filter.rb", "lib/rate_filter.rb", "lib/regioned_calculation.rb", "lib/repeating_filter.rb", "lib/settings.rb", "lib/single_time_filter.rb", "lib/tarif_builder.rb", "lib/weekday_filter.rb"]
|
13
|
+
s.files = ["CHANGELOG", "Manifest", "README.rdoc", "Rakefile", "lib/computer_class_filter.rb", "lib/deny_filter.rb", "lib/filter.rb", "lib/omega-tariffs-base.rb", "lib/packet_filter.rb", "lib/rate_filter.rb", "lib/regioned_calculation.rb", "lib/repeating_filter.rb", "lib/settings.rb", "lib/single_time_filter.rb", "lib/tarif_builder.rb", "lib/weekday_filter.rb", "spec/complex_tarif_spec.rb", "spec/computer_class_filter_spec.rb", "spec/deny_filter_spec.rb", "spec/filter_spec.rb", "spec/lib/active_record_classes_stub.rb", "spec/lib/init.rb", "spec/lib/spec_helper.rb", "spec/packet_filter_spec.rb", "spec/rate_filter_spec.rb", "spec/repeating_filter_spec.rb", "spec/settings_spec.rb", "spec/single_time_filter_spec.rb", "spec/tarif_builder_spec.rb", "spec/weekday_filter_spec.rb", "omega-tariffs-base.gemspec"]
|
14
|
+
s.homepage = %q{http://uniqsys.ru/}
|
15
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Omega-tariffs-base", "--main", "README.rdoc"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = %q{omega-tariffs-base}
|
18
|
+
s.rubygems_version = %q{1.3.7}
|
19
|
+
s.summary = %q{Omega Sector base tariffs}
|
20
|
+
|
21
|
+
if s.respond_to? :specification_version then
|
22
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
23
|
+
s.specification_version = 3
|
24
|
+
|
25
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
26
|
+
s.add_runtime_dependency(%q<activesupport>, ["= 2.3.8"])
|
27
|
+
s.add_development_dependency(%q<activesupport>, ["= 2.3.8"])
|
28
|
+
else
|
29
|
+
s.add_dependency(%q<activesupport>, ["= 2.3.8"])
|
30
|
+
s.add_dependency(%q<activesupport>, ["= 2.3.8"])
|
31
|
+
end
|
32
|
+
else
|
33
|
+
s.add_dependency(%q<activesupport>, ["= 2.3.8"])
|
34
|
+
s.add_dependency(%q<activesupport>, ["= 2.3.8"])
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'init'))
|
2
|
+
require 'mathn'
|
3
|
+
|
4
|
+
describe "Flat daily tarif" do
|
5
|
+
before do
|
6
|
+
@day_start = Time.utc(2010, 1, 1, 0, 0, 0)
|
7
|
+
@settings = Settings.new({
|
8
|
+
:klass => "RepeatingFilter",
|
9
|
+
:settings => {
|
10
|
+
{ :starts_at => @day_start + 8.hours, :length => 2.hours.to_i, :period => 1.day.to_i } =>
|
11
|
+
{ :klass => "RateFilter", :settings => { :rate => 95 } },
|
12
|
+
|
13
|
+
{ :starts_at => @day_start + 10.hours, :length => 4.hours.to_i, :period => 1.day.to_i } =>
|
14
|
+
{ :klass => "RateFilter", :settings => { :rate => 195 } },
|
15
|
+
|
16
|
+
{ :starts_at => @day_start + 14.hours, :length => 18.hours.to_i, :period => 1.day.to_i } =>
|
17
|
+
{ :klass => "RateFilter", :settings => { :rate => 395 } }
|
18
|
+
}
|
19
|
+
})
|
20
|
+
|
21
|
+
@filter = TarifBuilder.build(@settings, 0)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should calculate daily cost correctly" do
|
25
|
+
@filter.calculate_cost(1, 2, @day_start, 24*3600).should == 8080
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should calculate sliding fragment cost correctly" do
|
29
|
+
day_start = Time.now.utc.beginning_of_day
|
30
|
+
fragment = (Time.now.utc.beginning_of_day ... Time.now.utc.beginning_of_day + 4123)
|
31
|
+
|
32
|
+
zones = {
|
33
|
+
(day_start ... day_start + 8.hours) => 395,
|
34
|
+
(day_start + 8.hours ... day_start + 10.hours) => 95,
|
35
|
+
(day_start + 10.hours ... day_start + 14.hours) => 195,
|
36
|
+
(day_start + 14.hours ... day_start + 24.hours) => 395,
|
37
|
+
|
38
|
+
(day_start + 24.hours ... day_start + 24.hours + 8.hours) => 395,
|
39
|
+
(day_start + 24.hours + 8.hours ... day_start + 24.hours + 10.hours) => 95,
|
40
|
+
(day_start + 24.hours + 10.hours ... day_start + 24.hours + 14.hours) => 195,
|
41
|
+
(day_start + 24.hours + 14.hours ... day_start + 24.hours + 24.hours) => 395
|
42
|
+
}
|
43
|
+
|
44
|
+
while fragment.begin.wday == Time.now.utc.wday
|
45
|
+
real_summ = zones.map { |zone, price|
|
46
|
+
intersection = (zone & fragment) || (0...0)
|
47
|
+
Rational((intersection.end.to_i - intersection.begin.to_i) * price, 3600)
|
48
|
+
}.inject(&:+)
|
49
|
+
|
50
|
+
(@filter.calculate_cost(1, 2, fragment.begin, (fragment.end-fragment.begin).to_i)*100).to_i.should == (real_summ*100).to_i
|
51
|
+
fragment = Range.new(fragment.begin + 117.seconds, fragment.end + 127.seconds, true)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'init'))
|
2
|
+
|
3
|
+
class ComputerClassFilterMockFilter1 < Filter
|
4
|
+
def calculate_cost(computer_id, login_id, started_at, amount, options={})
|
5
|
+
Rational(amount.to_i*100, 3600)
|
6
|
+
end
|
7
|
+
|
8
|
+
def process_activity(unprocessed_activity, ft, options={})
|
9
|
+
ft = ft.dup
|
10
|
+
ft.volume = Rational(unprocessed_activity.amount.to_i*100, 3600)
|
11
|
+
ft.save!
|
12
|
+
true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ComputerClassFilterMockFilter2 < Filter
|
17
|
+
def calculate_cost(computer_id, login_id, started_at, amount, options={})
|
18
|
+
Rational(amount.to_i*200, 3600)
|
19
|
+
end
|
20
|
+
|
21
|
+
def process_activity(unprocessed_activity, ft, options={})
|
22
|
+
ft = ft.dup
|
23
|
+
ft.volume = Rational(unprocessed_activity.amount.to_i*200, 3600)
|
24
|
+
ft.save!
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "ComputerClassFilter" do
|
30
|
+
before do
|
31
|
+
settings = {
|
32
|
+
:klass => "ComputerClassFilter",
|
33
|
+
:settings => {
|
34
|
+
1 => { :klass => "ComputerClassFilterMockFilter1", :settings => { :vasya => "pupkin" } },
|
35
|
+
2 => { :klass => "ComputerClassFilterMockFilter2", :settings => { :vasya => "pupkin" } }
|
36
|
+
}
|
37
|
+
}
|
38
|
+
@filter = TarifBuilder.build Settings.new(settings), 0
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should fail to instantinate with incorrect config" do
|
42
|
+
settings = [ {
|
43
|
+
:klass => "ComputerClassFilter",
|
44
|
+
:settings => {
|
45
|
+
1 => { :klass => "ComputerClassFilterMockFilter1", :settings => { :vasya => "pupkin" } },
|
46
|
+
}
|
47
|
+
}, {
|
48
|
+
:klass => "ComputerClassFilter",
|
49
|
+
:settings => {
|
50
|
+
2 => { :klass => "ComputerClassFilterMockFilter1", :settings => { :vasya => "pupkin" } },
|
51
|
+
}
|
52
|
+
} ]
|
53
|
+
|
54
|
+
lambda {
|
55
|
+
TarifBuilder.build Settings.new(settings[0]), 0
|
56
|
+
}.should raise_error(Exception, /Some classes left undefined/)
|
57
|
+
|
58
|
+
lambda {
|
59
|
+
TarifBuilder.build Settings.new(settings[1]), 0
|
60
|
+
}.should raise_error(Exception, /Some classes left undefined/)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should direct calls to certain mock filters" do
|
64
|
+
{ 1 => ComputerClassFilterMockFilter1, 2 => ComputerClassFilterMockFilter2 }.each do |class_id, klass|
|
65
|
+
klass.any_instance.expects(:start_permitted?).with(class_id, anything, anything)
|
66
|
+
@filter.start_permitted?(class_id, DOESNT_MATTER)
|
67
|
+
|
68
|
+
klass.any_instance.expects(:permitted?).with(class_id, anything, anything)
|
69
|
+
@filter.permitted?(class_id, DOESNT_MATTER)
|
70
|
+
|
71
|
+
klass.any_instance.expects(:calculate_cost).with(class_id, anything, anything, anything, anything)
|
72
|
+
@filter.calculate_cost(class_id, DOESNT_MATTER, DOESNT_MATTER, DOESNT_MATTER, DOESNT_MATTER)
|
73
|
+
|
74
|
+
activity_doesnt_matter = Activity.new
|
75
|
+
activity_doesnt_matter.stubs(:created_at).returns(Time.now + 30.seconds)
|
76
|
+
activity_doesnt_matter.stubs(:amount).returns(3600)
|
77
|
+
activity_doesnt_matter.stubs(:computer_id).returns(class_id)
|
78
|
+
klass.any_instance.expects(:process_activity).with(anything, anything, anything)
|
79
|
+
@filter.process_activity(activity_doesnt_matter, DOESNT_MATTER, DOESNT_MATTER)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'init'))
|
2
|
+
|
3
|
+
describe "DenyFilter" do
|
4
|
+
before do
|
5
|
+
@filter = DenyFilter.new(stub(), 0)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should deny any service start" do
|
9
|
+
@filter.start_permitted?(1,2,3).should == false
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should deny any service conducting its work" do
|
13
|
+
@filter.permitted?(1,2,3).should == false
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return maximum price for any activity" do
|
17
|
+
@filter.calculate_cost(1, 2, 3, 4).should == Integer::MAX
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not deny activity processing" do
|
21
|
+
@filter.process_activity(stub(), stub()).should == true
|
22
|
+
end
|
23
|
+
end
|
data/spec/filter_spec.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'init'))
|
2
|
+
|
3
|
+
describe "Filter" do
|
4
|
+
it "should quack like a Filter :)" do
|
5
|
+
f = Filter.new(stub(), 0)
|
6
|
+
f.should respond_to :start_permitted?
|
7
|
+
f.should respond_to :permitted?
|
8
|
+
f.should respond_to :calculate_cost
|
9
|
+
f.should respond_to :process_activity
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should raise exceptions when called" do
|
13
|
+
f = Filter.new(stub(), 0)
|
14
|
+
|
15
|
+
lambda {
|
16
|
+
f.start_permitted?
|
17
|
+
}.should raise_error
|
18
|
+
|
19
|
+
lambda {
|
20
|
+
f.permitted?
|
21
|
+
}.should raise_error
|
22
|
+
|
23
|
+
lambda {
|
24
|
+
f.calculate_cost
|
25
|
+
}.should raise_error
|
26
|
+
|
27
|
+
lambda {
|
28
|
+
f.process_activity
|
29
|
+
}.should raise_error
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should call after_initialize" do
|
33
|
+
Filter.any_instance.expects(:after_initialize)
|
34
|
+
Filter.new(stub(), 0)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'activerecord', '=2.3.8'
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
class MockBase < ActiveRecord::Base
|
6
|
+
end
|
7
|
+
|
8
|
+
MockBase.class_eval do
|
9
|
+
alias_method :save, :valid?
|
10
|
+
|
11
|
+
def new_save!
|
12
|
+
raise "Invalid record: #{errors.full_messages.join("\n")}" unless valid?
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :save!, :new_save!
|
16
|
+
|
17
|
+
def self.columns()
|
18
|
+
@columns ||= [];
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.column(name, sql_type = nil, default = nil, null = true)
|
22
|
+
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type, null)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.template
|
26
|
+
instance = new
|
27
|
+
instance.instance_eval %Q{
|
28
|
+
def save
|
29
|
+
raise "Cannot save a template transaction"
|
30
|
+
end
|
31
|
+
|
32
|
+
def save!
|
33
|
+
raise "Cannot save a template transaction"
|
34
|
+
end
|
35
|
+
}
|
36
|
+
|
37
|
+
instance.expects(:save).never
|
38
|
+
instance.expects(:save!).never
|
39
|
+
|
40
|
+
instance
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class FinTransaction < MockBase
|
45
|
+
column :club_id, :integer
|
46
|
+
column :activity_id, :integer
|
47
|
+
column :author_id, :integer
|
48
|
+
column :src_account_id, :integer
|
49
|
+
column :dst_account_id, :integer
|
50
|
+
column :created_at, :timestamp
|
51
|
+
column :volume, :decimal
|
52
|
+
|
53
|
+
validates_presence_of :club_id
|
54
|
+
validates_presence_of :author_id
|
55
|
+
validates_presence_of :volume
|
56
|
+
|
57
|
+
class << self
|
58
|
+
attr_accessor :transactions
|
59
|
+
|
60
|
+
def reset_transactions
|
61
|
+
self.transactions = [ ]
|
62
|
+
end
|
63
|
+
|
64
|
+
def charge_login(login_id, volume)
|
65
|
+
FinTransaction.new(:author_id => 1, :club_id => 2,
|
66
|
+
:src_account_id => login_id, :dst_account_id => -1,
|
67
|
+
:volume => volume)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def save
|
72
|
+
return false unless valid?
|
73
|
+
do_save
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
def save!
|
78
|
+
raise "Validation error" unless valid?
|
79
|
+
do_save
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
def do_save
|
84
|
+
self.class.transactions ||= []
|
85
|
+
self.class.transactions << volume
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Activity < MockBase
|
90
|
+
column :created_at
|
91
|
+
column :login_id, :decimal
|
92
|
+
column :amount
|
93
|
+
column :computer_id
|
94
|
+
end
|
95
|
+
|
96
|
+
class Login
|
97
|
+
attr_accessor :id
|
98
|
+
|
99
|
+
def balance
|
100
|
+
return 0 unless self.class.balance
|
101
|
+
self.class.balance[id] || 0
|
102
|
+
end
|
103
|
+
|
104
|
+
class <<self
|
105
|
+
def find(id)
|
106
|
+
Login.new.tap do |login|
|
107
|
+
login.id = id
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
attr_accessor :balance
|
112
|
+
|
113
|
+
def set_login_balance(id, balance)
|
114
|
+
self.balance ||= { }
|
115
|
+
self.balance[id] = balance
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class Computer
|
121
|
+
attr_accessor :id
|
122
|
+
attr_accessor :computer_class_id
|
123
|
+
|
124
|
+
class <<self
|
125
|
+
def find(computer_id)
|
126
|
+
Computer.new.tap do |computer|
|
127
|
+
computer.id = computer_id
|
128
|
+
computer.computer_class_id = computer_id
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class ComputerClass
|
135
|
+
attr_accessor :id
|
136
|
+
|
137
|
+
class <<self
|
138
|
+
def count
|
139
|
+
all.size
|
140
|
+
end
|
141
|
+
|
142
|
+
def all
|
143
|
+
[ 1, 2 ].map { |id| with_id(id) }
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
def with_id(id)
|
148
|
+
ComputerClass.new.tap { |me| me.id = id}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|