bidding 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.
- checksums.yaml +7 -0
- data/README.md +121 -0
- data/lib/bidding/command.rb +36 -0
- data/lib/bidding/command_queue.rb +37 -0
- data/lib/bidding/commands/nop.rb +7 -0
- data/lib/bidding/commands_executor.rb +23 -0
- data/lib/bidding/in_memory_transaction_log.rb +30 -0
- data/lib/bidding/tools/exporter.rb +37 -0
- data/lib/bidding/tools/importer.rb +22 -0
- data/lib/bidding/transaction_log.rb +18 -0
- data/lib/bidding/version.rb +3 -0
- data/lib/bidding.rb +14 -0
- metadata +97 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 5d5697d795874fbac30fd7513e22eccaed0ff1dc
|
|
4
|
+
data.tar.gz: 6549800b7a60c5d993c263d84570ecafecfbb0e9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ed39d7e778f30cd4015923f8e8c20b39d2b4e6c13acca76a24fec18c42ae3d15557fa0055da96e668c70c209471c6827d81108be5a3dec1ae60cb92b06c8588a
|
|
7
|
+
data.tar.gz: e78b7fa2c025e9c80cc5d7fc1a6fc22bcc934ca6f91234d6262150a568bed697454035e0c647ec757d4ea8e6ab20fc7b070767a47d3f15c7bb45c78c18c39783
|
data/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Bidding
|
|
2
|
+
|
|
3
|
+
Bidding is a small gem for handling the command pattern.
|
|
4
|
+
The goal is to let the commands be easy to implement, and to provide basic infrastructure for executing commands.
|
|
5
|
+
|
|
6
|
+
The infrastructure is a command queue, a command executor which executes transactions and a transaction log which keeps a record of all executed commands.
|
|
7
|
+
|
|
8
|
+
It's inspried from the CQRS movement, except it is left up to the implementor to decide if the commands should issue events and thus use eventsourcing.
|
|
9
|
+
Or if it simple should keep a record of all executed commands.
|
|
10
|
+
|
|
11
|
+
The gem uses an in memory transaction log and queue. Right now the idea is that adapters to different queues and storage backed logs should be implemented outside the gem.
|
|
12
|
+
Thus keeping the gem dependencies as small as possible.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Add this line to your application's Gemfile:
|
|
17
|
+
|
|
18
|
+
gem 'bidding'
|
|
19
|
+
|
|
20
|
+
And then execute:
|
|
21
|
+
|
|
22
|
+
$ bundle
|
|
23
|
+
|
|
24
|
+
Or install it yourself as:
|
|
25
|
+
|
|
26
|
+
$ gem install bidding
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
When you require 'bidding' you get access to a bunch of classes.
|
|
31
|
+
The most important one is the Command class.
|
|
32
|
+
|
|
33
|
+
### Commands
|
|
34
|
+
To create a command simple inherit from the Command class and add an execute method.
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
|
|
38
|
+
class Nop < Command
|
|
39
|
+
|
|
40
|
+
def execute
|
|
41
|
+
# this code executes what the command does
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This will allow you to execute the nop command simply by parsing and executing the command.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
|
|
53
|
+
Command.parse("nop").execute()
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Parameters
|
|
58
|
+
|
|
59
|
+
A command wouldn't be much use unless it could take a few paramters. Since the focus here is to have the command code as readable as possible.
|
|
60
|
+
You simple specify the arguments of the command in order in which they appear.
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
|
|
64
|
+
class Nop < Command
|
|
65
|
+
|
|
66
|
+
parameters :type, :id, :title
|
|
67
|
+
|
|
68
|
+
def execute
|
|
69
|
+
# this code executes what the command does
|
|
70
|
+
# you can use type, id and title here.
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
the parameters method adds properties to the command which correspond to the location of the arguments in the commandline string.
|
|
78
|
+
The code above has 3 parameters: type, id and title. These are all available in the execute method.
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
|
|
84
|
+
class Nop < Command
|
|
85
|
+
|
|
86
|
+
parameters :type, :id, :title
|
|
87
|
+
|
|
88
|
+
def execute
|
|
89
|
+
p type
|
|
90
|
+
p id
|
|
91
|
+
p URI.unescape(title)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
Command.parse("nop the_type 12345 awesome%20stuff!").execute()
|
|
97
|
+
|
|
98
|
+
# outputs
|
|
99
|
+
# > the_type
|
|
100
|
+
# > 12345
|
|
101
|
+
# > awesome!
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Each parameter is separated with a space. If you would like to add parameter values that contain spaces I recommend URI encoding them first.
|
|
106
|
+
This allows you to handle strings that might contain spaces.
|
|
107
|
+
|
|
108
|
+
### Executor
|
|
109
|
+
|
|
110
|
+
The commands executor simply takes a hash of commands from a queue and executes them in order. Successfull commands are pushed to a log.
|
|
111
|
+
If the commands are not successfull the commands are left on the queue.
|
|
112
|
+
|
|
113
|
+
The gem contains an inmemory queue and log for testing purposes.
|
|
114
|
+
|
|
115
|
+
## Contributing
|
|
116
|
+
|
|
117
|
+
1. Fork it ( http://github.com/morkeleb/bidding/fork )
|
|
118
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
119
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
120
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
121
|
+
5. Create new Pull Request
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
class Command
|
|
2
|
+
attr_accessor :name, :arguments, :user, :commandline
|
|
3
|
+
|
|
4
|
+
def self.parameters(*args)
|
|
5
|
+
args.each do |arg|
|
|
6
|
+
self.class_eval("def #{arg};arguments["+args.index(arg).to_s+"];end")
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.parse(commandline, user)
|
|
11
|
+
parts = commandline.split(" ")
|
|
12
|
+
name = camel_case parts.shift
|
|
13
|
+
command = Kernel.const_get(name).new
|
|
14
|
+
command.name = name
|
|
15
|
+
command.user = user
|
|
16
|
+
command.arguments = parts
|
|
17
|
+
command.commandline = commandline
|
|
18
|
+
return command
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.camel_case(s)
|
|
22
|
+
return s if s !~ /_/ && s =~ /[A-Z]+.*/
|
|
23
|
+
s.split('_').map{|e| e.capitalize}.join
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def execute
|
|
28
|
+
raise 'execute method not implemented for class: ' + self.class.name
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def replay
|
|
32
|
+
@replay = true
|
|
33
|
+
execute
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'observer'
|
|
2
|
+
|
|
3
|
+
class CommandQueue
|
|
4
|
+
include Observable
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@list = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def list
|
|
11
|
+
@list
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pushCommands(commands)
|
|
15
|
+
@list.push commands
|
|
16
|
+
changed
|
|
17
|
+
notify_observers commands
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def length
|
|
22
|
+
@list.length
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def return(transaction)
|
|
26
|
+
@list.insert(0, transaction)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def next
|
|
30
|
+
@list.shift
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def exists? (id)
|
|
34
|
+
@list.index { |item| item[:id] == id} != nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class CommandsExecutor
|
|
2
|
+
def initialize(queue, transactionLog)
|
|
3
|
+
@queue = queue
|
|
4
|
+
@transactionLog = transactionLog
|
|
5
|
+
@queue.add_observer(self)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def update(commands)
|
|
9
|
+
transaction = @queue.next
|
|
10
|
+
transaction["commands"].each {|c|
|
|
11
|
+
co = Command.parse(c, transaction["user"])
|
|
12
|
+
co.execute()
|
|
13
|
+
}
|
|
14
|
+
@transactionLog.push transaction
|
|
15
|
+
rescue Exception
|
|
16
|
+
p 'EXCEPTION'
|
|
17
|
+
p $!.to_s
|
|
18
|
+
p $!.backtrace
|
|
19
|
+
@queue.return transaction
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class InMemoryTransactionLog
|
|
2
|
+
|
|
3
|
+
def initialize
|
|
4
|
+
@list = []
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def push(transaction)
|
|
8
|
+
@list.push(transaction)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def from(date)
|
|
12
|
+
@list.select { |entry| entry["date"] < date}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def containsId? (id)
|
|
17
|
+
@list.index { |trans| trans["id"] == id } != nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def delete(entry)
|
|
21
|
+
@list.delete_if { |entry| entry["id"] == entry}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# for testing purposes only
|
|
25
|
+
def log
|
|
26
|
+
@list
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'date'
|
|
3
|
+
|
|
4
|
+
class Exporter
|
|
5
|
+
|
|
6
|
+
def initialize(log)
|
|
7
|
+
@log = log
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def export_from(date)
|
|
11
|
+
entries = (@log.from date.to_f).to_a
|
|
12
|
+
write entries, to(file_by date)
|
|
13
|
+
|
|
14
|
+
delete entries
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def write(entries, path)
|
|
18
|
+
File.open(path, "w") { |file|
|
|
19
|
+
file.write JSON entries
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def delete(entries)
|
|
24
|
+
entries.each do |entry|
|
|
25
|
+
@log.delete entry
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to(file)
|
|
30
|
+
file
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def file_by(date)
|
|
34
|
+
return "./tmp/log-" + Time.now.utc.to_date.to_s + ".json"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
Dir["./lib/commands/**/*.rb"].sort.each {|f| require f}
|
|
3
|
+
Dir["./lib/models/**/*.rb"].sort.each {|f| require f}
|
|
4
|
+
class Importer
|
|
5
|
+
|
|
6
|
+
def import file
|
|
7
|
+
p 'importing file ' + file
|
|
8
|
+
transaction = JSON File.read file
|
|
9
|
+
|
|
10
|
+
transaction.each { |trans|
|
|
11
|
+
commands = trans["commands"]
|
|
12
|
+
commands.each { |command_string|
|
|
13
|
+
command = Command.parse command_string, trans["user"]
|
|
14
|
+
command.replay
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
File.delete file
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'date'
|
|
2
|
+
|
|
3
|
+
class TransactionLog
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def adapter=(adapter)
|
|
7
|
+
@adapter = adapter
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def adapter
|
|
11
|
+
@adapter ||= InMemoryTransactionLog.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def push(transaction)
|
|
15
|
+
transaction["date"] = Time.now.utc.to_f
|
|
16
|
+
adapter.push(transaction)
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/bidding.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require "bidding/version"
|
|
2
|
+
require 'bidding/command'
|
|
3
|
+
require 'bidding/command_queue'
|
|
4
|
+
require 'bidding/commands_executor'
|
|
5
|
+
require 'bidding/transaction_log'
|
|
6
|
+
require 'bidding/in_memory_transaction_log'
|
|
7
|
+
require 'bidding/commands/nop'
|
|
8
|
+
require 'bidding/tools/exporter'
|
|
9
|
+
require 'bidding/tools/importer'
|
|
10
|
+
|
|
11
|
+
module Bidding
|
|
12
|
+
# Your code goes here...
|
|
13
|
+
|
|
14
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: bidding
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Morten Nielsen
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2014-12-18 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ~>
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.5'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ~>
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.5'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ~>
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.6'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ~>
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.6'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - '>='
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - '>='
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
description: Lets you create commands that will parse from oneline strings, and execute
|
|
56
|
+
them.
|
|
57
|
+
email: morten@morkeleb.com
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- README.md
|
|
63
|
+
- lib/bidding.rb
|
|
64
|
+
- lib/bidding/command.rb
|
|
65
|
+
- lib/bidding/command_queue.rb
|
|
66
|
+
- lib/bidding/commands/nop.rb
|
|
67
|
+
- lib/bidding/commands_executor.rb
|
|
68
|
+
- lib/bidding/in_memory_transaction_log.rb
|
|
69
|
+
- lib/bidding/tools/exporter.rb
|
|
70
|
+
- lib/bidding/tools/importer.rb
|
|
71
|
+
- lib/bidding/transaction_log.rb
|
|
72
|
+
- lib/bidding/version.rb
|
|
73
|
+
homepage: https://github.com/morkeleb/bidding
|
|
74
|
+
licenses:
|
|
75
|
+
- MIT
|
|
76
|
+
metadata: {}
|
|
77
|
+
post_install_message:
|
|
78
|
+
rdoc_options: []
|
|
79
|
+
require_paths:
|
|
80
|
+
- lib
|
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
82
|
+
requirements:
|
|
83
|
+
- - '>='
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '0'
|
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - '>='
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '0'
|
|
91
|
+
requirements: []
|
|
92
|
+
rubyforge_project:
|
|
93
|
+
rubygems_version: 2.2.2
|
|
94
|
+
signing_key:
|
|
95
|
+
specification_version: 4
|
|
96
|
+
summary: A small library for handling commands
|
|
97
|
+
test_files: []
|