omega-tariffs-base 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|