poros 0.1.0
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/.gitignore +2 -0
- data/.travis.yml +9 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +91 -0
- data/Rakefile +10 -0
- data/lib/poros.rb +198 -0
- data/lib/poros/version.rb +3 -0
- data/poros.gemspec +27 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3c1887d3665a8d34d90f44b16382afdd2863180aa1bbfdb3f065c2f417ad55e7
|
4
|
+
data.tar.gz: b80355221534570d2a2f78c98d4cb26397176c04967e618cd496a95e967c77fd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b8293c3a1e811aeef5df450149e4d8e2f7735b6f222ae0aa9e29efc5f136421adb42697c2b41d975abb563dc25e21ce9ce7a53bea319f703f1799207fe8c263c
|
7
|
+
data.tar.gz: d48e5df3094177dc7ca4bf4e0fde76bd7ed860da99acb4f9d19b5081cebed97d478ce4a8fd0a6959d886dfcceb03c4c4872a96565dd8ffb7bb3d01e3418534b8
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Sean Ross Earle
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# Poros 
|
2
|
+
|
3
|
+
This is a little gem written for persistence and active-record like querying of
|
4
|
+
objects with a simple YAML based backend.
|
5
|
+
|
6
|
+
The name stands for Plain Old Ruby Object Storage.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
This gem requires at least Ruby 2.1 because it uses `.to_h` on an Array and
|
11
|
+
named arguments for initialize.
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```gem 'poros' ```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install poros
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
A basic usage would look like this:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class Widget
|
31
|
+
include Poros
|
32
|
+
|
33
|
+
poro_attr :name, :order, :active
|
34
|
+
poro_index :active
|
35
|
+
|
36
|
+
def initialize(name: '', order: 0, active: false)
|
37
|
+
@name = name
|
38
|
+
@order = order
|
39
|
+
@active = active
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
widget = Widget.new(name: 'Cube', order: 0, active: true)
|
44
|
+
widget.save
|
45
|
+
|
46
|
+
other_widget = Widget.find('uuid')
|
47
|
+
|
48
|
+
many_widgets = Widget.where(order: 1, name: /regex/)
|
49
|
+
```
|
50
|
+
|
51
|
+
`#initialize` must use keyword arguments and contain at least all the
|
52
|
+
attributes defined in `poro_attr`. In the future I may define this function for
|
53
|
+
you but for right now I'm not sure I want to go that direction.
|
54
|
+
|
55
|
+
`poro_attrs :blah` will define a getter and setter for `:blah` and also be persisted when you run `#save`
|
56
|
+
|
57
|
+
`poro_indexes :blah` will create indexes on these columns, this will greatly
|
58
|
+
speed up searching but if your index gets really big the initial load of a
|
59
|
+
model will take quite a long time.
|
60
|
+
|
61
|
+
`self.data_directory` you can re-define this method if you don't want it to
|
62
|
+
save to the default of `./db/#{self}`. If you do re-define it you _must_
|
63
|
+
seperate each model into it's own folder or it will have _lots_ of issues.
|
64
|
+
|
65
|
+
`.where` acts a bit like ActiveRecord but it's not currently chainable (I plan
|
66
|
+
to do that in the future though) and currently can only take exact matches or
|
67
|
+
regex, I plan to add array in the near future.
|
68
|
+
|
69
|
+
`.find` takes the `#uuid` which is generated by Poros and returns the single record.
|
70
|
+
|
71
|
+
## Development
|
72
|
+
|
73
|
+
After checking out the repo, run `bundle install` to install dependencies. Then, run
|
74
|
+
`rake test` to run the tests. You can also run `bin/console` for an interactive
|
75
|
+
prompt that will allow you to experiment.
|
76
|
+
|
77
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
78
|
+
release a new version, update the version number in `version.rb`, and then run
|
79
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
80
|
+
git commits and tags, and push the `.gem` file to
|
81
|
+
[rubygems.org](https://rubygems.org).
|
82
|
+
|
83
|
+
## Contributing
|
84
|
+
|
85
|
+
Bug reports and pull requests are welcome on GitHub at
|
86
|
+
https://github.com/HellRok/Poros
|
87
|
+
|
88
|
+
## License
|
89
|
+
|
90
|
+
The gem is available as open source under the terms of the [MIT
|
91
|
+
License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/lib/poros.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module Poros
|
5
|
+
attr_accessor :uuid
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
extend ClassMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def uuid
|
13
|
+
@uuid ||= SecureRandom.uuid
|
14
|
+
end
|
15
|
+
|
16
|
+
def poros
|
17
|
+
@poros ||= PorosInfo.new(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def destroy
|
21
|
+
File.delete(poros.file_path)
|
22
|
+
self.class.remove_from_index(self)
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def save
|
27
|
+
File.write(poros.file_path, poros.to_h.to_yaml)
|
28
|
+
self.class.data_changed = true
|
29
|
+
self.class.update_index(self)
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
class PorosInfo
|
34
|
+
def initialize(object)
|
35
|
+
@object = object
|
36
|
+
end
|
37
|
+
|
38
|
+
def file_path
|
39
|
+
@object.class.file_path(@object.uuid)
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_h
|
43
|
+
@object.class.poro_attrs.map { |column|
|
44
|
+
[column, @object.send(column.to_s)]
|
45
|
+
}.to_h
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module ClassMethods
|
50
|
+
attr_accessor :in_transaction, :data_changed
|
51
|
+
|
52
|
+
def poro_attr(*attrs)
|
53
|
+
@poro_attrs = [:uuid] | attrs
|
54
|
+
attrs.each { |column|
|
55
|
+
class_eval "attr_accessor :#{column}"
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def poro_attrs
|
60
|
+
@poro_attrs ||= []
|
61
|
+
end
|
62
|
+
|
63
|
+
def poro_index(*attrs)
|
64
|
+
@poro_indexes ||= []
|
65
|
+
@poro_indexes += attrs
|
66
|
+
end
|
67
|
+
|
68
|
+
def poro_indexes
|
69
|
+
@poro_indexes ||= []
|
70
|
+
end
|
71
|
+
|
72
|
+
def find(uuid)
|
73
|
+
attrs = YAML.load(File.read(file_path(uuid)))
|
74
|
+
attrs.delete(:uuid)
|
75
|
+
|
76
|
+
object = new(attrs)
|
77
|
+
object.uuid = uuid
|
78
|
+
object
|
79
|
+
end
|
80
|
+
|
81
|
+
def file_path(uuid)
|
82
|
+
FileUtils.mkdir_p(data_directory) unless File.exist?(data_directory)
|
83
|
+
File.join(data_directory, file_name(uuid))
|
84
|
+
end
|
85
|
+
|
86
|
+
def data_directory
|
87
|
+
"./db/#{self}/"
|
88
|
+
end
|
89
|
+
|
90
|
+
def file_name(uuid)
|
91
|
+
"#{uuid}.yml"
|
92
|
+
end
|
93
|
+
|
94
|
+
def index_file
|
95
|
+
File.join(data_directory, "indexes.yml")
|
96
|
+
end
|
97
|
+
|
98
|
+
def all
|
99
|
+
Dir.glob(File.join(data_directory, '*.yml')).map { |file|
|
100
|
+
next if file == index_file
|
101
|
+
data = YAML.load(File.read(file))
|
102
|
+
find(data[:uuid])
|
103
|
+
}.compact
|
104
|
+
end
|
105
|
+
|
106
|
+
def where(query)
|
107
|
+
indexed, table_scan = query.partition { |index, key| poro_indexes.include?(index) }
|
108
|
+
|
109
|
+
indexed_results = indexed.map { |key, value|
|
110
|
+
case value
|
111
|
+
when Regexp
|
112
|
+
index_data[key].keys.flat_map { |value_name|
|
113
|
+
index_data[key][value_name] if value =~ value_name
|
114
|
+
}.compact
|
115
|
+
else
|
116
|
+
index_data[key].has_key?(value) ?
|
117
|
+
index_data[key][value] : []
|
118
|
+
end
|
119
|
+
}.inject(:&)
|
120
|
+
|
121
|
+
if table_scan.size > 0
|
122
|
+
scanned_results = Dir.glob(File.join(data_directory, '*.yml')).map { |file|
|
123
|
+
data = YAML.load(File.read(file))
|
124
|
+
data[:uuid] if table_scan.all? { |key, value|
|
125
|
+
case value
|
126
|
+
when Regexp
|
127
|
+
value =~ data[key]
|
128
|
+
else
|
129
|
+
data[key] == value
|
130
|
+
end
|
131
|
+
}
|
132
|
+
}.compact
|
133
|
+
end
|
134
|
+
|
135
|
+
if indexed.size > 0 && table_scan.size > 0
|
136
|
+
results = indexed_results & scanned_results
|
137
|
+
elsif indexed.size > 0
|
138
|
+
results = indexed_results
|
139
|
+
else
|
140
|
+
results = scanned_results
|
141
|
+
end
|
142
|
+
|
143
|
+
results.map { |uuid| find(uuid) }
|
144
|
+
end
|
145
|
+
|
146
|
+
def index_data
|
147
|
+
return @index_data if @index_data
|
148
|
+
|
149
|
+
data = File.exist?(index_file) ? YAML.load(File.read(index_file)) : {}
|
150
|
+
# Make sure we always have every index as a key
|
151
|
+
poro_indexes.each do |index|
|
152
|
+
data[index] = {} unless data.has_key?(index)
|
153
|
+
end
|
154
|
+
|
155
|
+
@index_data = data
|
156
|
+
end
|
157
|
+
|
158
|
+
def write_index_data
|
159
|
+
File.write(index_file, @index_data.to_yaml)
|
160
|
+
end
|
161
|
+
|
162
|
+
def update_index(object)
|
163
|
+
remove_from_index(object, false)
|
164
|
+
|
165
|
+
index_data
|
166
|
+
|
167
|
+
poro_indexes.each do |index|
|
168
|
+
@index_data[index] = {} unless @index_data.has_key?(index)
|
169
|
+
value = object.send(index)
|
170
|
+
@index_data[index][value] ||= []
|
171
|
+
@index_data[index][value] = @index_data[index][value] | [object.uuid]
|
172
|
+
end
|
173
|
+
|
174
|
+
write_index_data unless @in_transaction
|
175
|
+
end
|
176
|
+
|
177
|
+
def remove_from_index(object, perist = true)
|
178
|
+
index_data
|
179
|
+
|
180
|
+
poro_indexes.each do |index|
|
181
|
+
@index_data[index] = {} unless @index_data.has_key?(index)
|
182
|
+
@index_data[index].keys.each do |value|
|
183
|
+
@index_data[index][value] ||= []
|
184
|
+
@index_data[index][value] -= [object.uuid]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
write_index_data if !@in_transaction && perist
|
188
|
+
end
|
189
|
+
|
190
|
+
def transaction(&block)
|
191
|
+
@in_transaction = true
|
192
|
+
@data_changed = false
|
193
|
+
block.call
|
194
|
+
@in_transaction = false
|
195
|
+
write_index_data if @data_changed
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
data/poros.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require "poros/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'poros'
|
8
|
+
spec.version = Poros::VERSION
|
9
|
+
spec.date = '2018-04-22'
|
10
|
+
spec.summary = 'Persist and query your objects'
|
11
|
+
spec.description = 'Persist your objects and query them in an active record like way'
|
12
|
+
spec.authors = ["Sean Ross Earle"]
|
13
|
+
spec.email = ["sean.earle@oeQuacki.com"]
|
14
|
+
spec.homepage = "https://github.com/HellRok/YAMLCache"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: poros
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sean Ross Earle
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-04-22 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.16'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
description: Persist your objects and query them in an active record like way
|
56
|
+
email:
|
57
|
+
- sean.earle@oeQuacki.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- lib/poros.rb
|
69
|
+
- lib/poros/version.rb
|
70
|
+
- poros.gemspec
|
71
|
+
homepage: https://github.com/HellRok/YAMLCache
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 2.7.6
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: Persist and query your objects
|
95
|
+
test_files: []
|