buzztools 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/buzztools/config.rb +114 -0
- data/lib/buzztools/extend_array.rb +23 -0
- data/lib/buzztools/extend_bignum.rb +1 -1
- data/lib/buzztools/extend_object.rb +9 -6
- data/lib/buzztools/extend_string.rb +15 -0
- data/lib/buzztools/extend_time.rb +10 -0
- data/lib/buzztools/extras/shell_extras.rb +79 -0
- data/lib/buzztools/file.rb +123 -0
- data/lib/buzztools/version.rb +1 -1
- data/lib/buzztools/versionary.rb +163 -0
- metadata +21 -5
- checksums.yaml +0 -15
@@ -0,0 +1,114 @@
|
|
1
|
+
module Buzztools
|
2
|
+
class Config < Hash
|
3
|
+
|
4
|
+
attr_reader :default_values
|
5
|
+
|
6
|
+
def initialize(aDefaultValues,aNewValues=nil,&aBlock)
|
7
|
+
@default_values = aDefaultValues.clone
|
8
|
+
reset()
|
9
|
+
if aNewValues
|
10
|
+
block_given? ? read(aNewValues,&aBlock) : read(aNewValues)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# aBlock allows values to be filtered based on key,default and new values
|
15
|
+
def read(aSource,&aBlock)
|
16
|
+
default_values.each do |k,v|
|
17
|
+
done = false
|
18
|
+
if block_given? && ((newv = yield(k,v,aSource && aSource[k])) != nil)
|
19
|
+
self[k] = newv
|
20
|
+
done = true
|
21
|
+
end
|
22
|
+
copy_item(aSource,k) if !done && aSource && !aSource[k].nil?
|
23
|
+
end
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# reset values back to defaults
|
28
|
+
def reset
|
29
|
+
self.clear
|
30
|
+
me = self
|
31
|
+
@default_values.each {|n,v| me[n] = v.is_a?(Class) ? nil : v}
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_int(aKey,aValue)
|
35
|
+
case aValue
|
36
|
+
when String then self[aKey] = aValue.to_integer(self[aKey]);
|
37
|
+
when Fixnum then self[aKey] = aValue;
|
38
|
+
when Float then self[aKey] = aValue.to_i;
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_float(aKey,aValue)
|
43
|
+
case aValue
|
44
|
+
when String then self[aKey] = aValue.to_float(self[aKey]);
|
45
|
+
when Fixnum then self[aKey] = aValue.to_f;
|
46
|
+
when Float then self[aKey] = aValue;
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_boolean(aKey,aValue)
|
51
|
+
case aValue
|
52
|
+
when TrueClass,FalseClass then self[aKey] = aValue;
|
53
|
+
when String then self[aKey] = (['1','yes','y','true','on'].include?(aValue.downcase))
|
54
|
+
else
|
55
|
+
set_boolean(aKey,aValue.to_s)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_symbol(aKey,aValue)
|
60
|
+
case aValue
|
61
|
+
when String then self[aKey] = (aValue.to_sym rescue nil);
|
62
|
+
when Symbol then self[aKey] = aValue;
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def copy_item(aHash,aKey)
|
67
|
+
d = default_values[aKey]
|
68
|
+
d_class = (d.is_a?(Class) ? d : d.class)
|
69
|
+
cname = d_class.name.to_sym
|
70
|
+
case cname
|
71
|
+
when :NilClass then self[aKey] = aHash[aKey];
|
72
|
+
when :String then self[aKey] = aHash[aKey].to_s unless aHash[aKey].nil?
|
73
|
+
when :Float then set_float(aKey,aHash[aKey]);
|
74
|
+
when :Fixnum then set_int(aKey,aHash[aKey]);
|
75
|
+
when :TrueClass, :FalseClass then set_boolean(aKey,aHash[aKey]);
|
76
|
+
when :Symbol then self[aKey] = (aHash[aKey].to_sym rescue nil)
|
77
|
+
when :Proc then self[aKey] = aHash[aKey] if aHash[aKey].is_a?(Proc)
|
78
|
+
else
|
79
|
+
raise StandardError.new('unsupported type')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def copy_strings(aHash,*aKeys)
|
84
|
+
aKeys.each do |k|
|
85
|
+
self[k] = aHash[k].to_s unless aHash[k].nil?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def copy_ints(*aDb)
|
90
|
+
aHash = aDb.shift
|
91
|
+
aKeys = aDb
|
92
|
+
aKeys.each do |k|
|
93
|
+
set_int(k,aHash[k])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def copy_floats(aHash,*aKeys)
|
98
|
+
aKeys.each do |k|
|
99
|
+
set_float(k,aHash[k])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def copy_booleans(aHash,*aKeys)
|
104
|
+
aKeys.each do |k|
|
105
|
+
set_boolean(k,aHash[k])
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_hash
|
110
|
+
{}.merge(self)
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
1
3
|
module ExtendArray
|
2
4
|
|
3
5
|
module_function # this makes the methods accessible on the module as well as instances when the module is included into a class
|
@@ -41,6 +43,27 @@ module ExtendArray
|
|
41
43
|
def to_nil
|
42
44
|
self.empty? ? nil : self
|
43
45
|
end
|
46
|
+
|
47
|
+
def to_csv
|
48
|
+
def as_hash(aItem)
|
49
|
+
aItem = aItem.attributes if aItem.respond_to?(:attributes)
|
50
|
+
return aItem if aItem.is_a?(Hash)
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
item1 = as_hash(first)
|
54
|
+
raise "Must be an array of hashes" unless item1 && item1.is_a?(Hash)
|
55
|
+
fields = item1.keys.map(&:to_s).sort
|
56
|
+
if fields.delete('id')
|
57
|
+
fields.unshift('id')
|
58
|
+
end
|
59
|
+
CSV.generate do |csv|
|
60
|
+
csv << fields
|
61
|
+
self.each do |i|
|
62
|
+
next unless i = as_hash(i)
|
63
|
+
csv << i.values_at(*column_names)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
44
67
|
end
|
45
68
|
|
46
69
|
Array.class_eval do
|
@@ -24,21 +24,24 @@ module ExtendObject
|
|
24
24
|
path = args
|
25
25
|
end
|
26
26
|
if path.is_a?(String)
|
27
|
-
segments = path.split('.')
|
27
|
+
segments = path.split('.')
|
28
28
|
segment = segments.shift
|
29
29
|
elsif path.is_a?(Symbol)
|
30
|
-
|
31
|
-
|
30
|
+
path = path.to_s
|
31
|
+
segments = [path]
|
32
|
+
segment = segments.shift
|
32
33
|
elsif path.is_a?(Array)
|
33
34
|
segments = path
|
34
35
|
segment = segments.shift
|
35
|
-
segment = segment.to_sym if segment
|
36
|
+
#segment = segment.to_sym if segment
|
36
37
|
end
|
37
38
|
return self unless segment.to_nil
|
38
|
-
value = if self.
|
39
|
+
value = if self.is_a?(Array)
|
40
|
+
self[segment.to_i] rescue nil
|
41
|
+
elsif (self.respond_to?(segment.to_sym) rescue nil)
|
39
42
|
self.send(segment)
|
40
43
|
elsif self.respond_to?(:[])
|
41
|
-
(self[segment] || self[segment.
|
44
|
+
(begin self[segment] rescue nil end) || (begin self[segment.to_sym] rescue nil end)
|
42
45
|
end
|
43
46
|
if segments.empty?
|
44
47
|
value
|
@@ -215,6 +215,21 @@ String.class_eval do
|
|
215
215
|
def self.from_file(aFilename)
|
216
216
|
File.open(aFilename, "rb") { |f| f.read }
|
217
217
|
end
|
218
|
+
|
219
|
+
# given ('abcdefg','c.*?e') returns ['ab','cde','fg'] so you can manipulate the head, match and tail seperately, and potentially rejoin
|
220
|
+
def split3(aPattern,aOccurence=0)
|
221
|
+
aString = self
|
222
|
+
matches = aString.scan_md(aPattern)
|
223
|
+
match = matches[aOccurence]
|
224
|
+
parts = (match ? [match.pre_match,match.to_s,match.post_match] : [aString,nil,''])
|
225
|
+
|
226
|
+
if !block_given? # return head,match,tail
|
227
|
+
parts
|
228
|
+
else # return string
|
229
|
+
parts[1] = yield *parts if match
|
230
|
+
parts.join
|
231
|
+
end
|
232
|
+
end
|
218
233
|
end
|
219
234
|
|
220
235
|
|
@@ -81,4 +81,14 @@ Time.class_eval do
|
|
81
81
|
def to_w3c
|
82
82
|
utc.strftime("%Y-%m-%dT%H:%M:%S+00:00")
|
83
83
|
end
|
84
|
+
|
85
|
+
# returns an integer date stamp (milliseconds since 1970) compatible with Javascript
|
86
|
+
def to_ms
|
87
|
+
(to_f*1000).round
|
88
|
+
end
|
89
|
+
|
90
|
+
# creates a Time object from an integer date stamp (milliseconds since 1970) compatible with Javascript
|
91
|
+
def self.from_ms(aMilliseconds)
|
92
|
+
at(aMilliseconds/1000.0)
|
93
|
+
end
|
84
94
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
gem 'POpen4'; require 'popen4'
|
2
|
+
|
3
|
+
module POpen4
|
4
|
+
|
5
|
+
class ExecuteError < StandardError
|
6
|
+
|
7
|
+
attr_reader :result #,:stderr,:stdout,:exitcode,:pid
|
8
|
+
|
9
|
+
def initialize(aArg)
|
10
|
+
if aArg.is_a? Hash
|
11
|
+
msg = ([aArg[:stderr],aArg[:stdout],"Error #{aArg[:exitcode].to_s}"].find {|i| i && !i.empty?})
|
12
|
+
super(msg)
|
13
|
+
@result = aArg
|
14
|
+
else
|
15
|
+
super(aArg)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
"#{self.class.to_s}: #{@result.inspect}"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.pump_thread(aIn,aOut)
|
26
|
+
Thread.new do
|
27
|
+
loop { aOut.puts aIn.gets }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Usage :
|
32
|
+
# result = POpen4::shell('somebinary') do |r| # block gives opportunity to adjust result, and avoid exception raised from non-zero exit codes
|
33
|
+
# if r[:exitcode]==254 # eg. say this binary returns 254 to mean something special but not an error
|
34
|
+
# r[:stdout] = 'some correct output'
|
35
|
+
# r[:stderr] = ''
|
36
|
+
# r[:exitcode] = 0
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# OR
|
41
|
+
#
|
42
|
+
# result = POpen4::shell('somebinary');
|
43
|
+
# puts result[:stdout]
|
44
|
+
#
|
45
|
+
# Giving aStdOut,aStdErr causes the command output to be connected to the given stream, and that stream to not be given in the result hash
|
46
|
+
def self.shell(aCommand,aWorkingDir=nil,aTimeout=nil,aStdOut=nil,aStdErr=nil)
|
47
|
+
raise ExecuteError.new('aWorkingDir doesnt exist') unless !aWorkingDir || File.exists?(aWorkingDir)
|
48
|
+
orig_wd = Dir.getwd
|
49
|
+
result = {:command => aCommand, :dir => (aWorkingDir || orig_wd)}
|
50
|
+
status = nil
|
51
|
+
begin
|
52
|
+
Dir.chdir(aWorkingDir) if aWorkingDir
|
53
|
+
Timeout.timeout(aTimeout,ExecuteError) do # nil aTimeout will not time out
|
54
|
+
status = POpen4::popen4(aCommand) do |stdout, stderr, stdin, pid|
|
55
|
+
thrOut = aStdOut ? Thread.new { aStdOut.puts stdout.read } : nil
|
56
|
+
thrErr = aStdErr ? Thread.new { aStdErr.puts stderr.read } : nil
|
57
|
+
thrOut.join if thrOut
|
58
|
+
thrErr.join if thrErr
|
59
|
+
|
60
|
+
result[:stdout] = stdout.read unless aStdOut
|
61
|
+
result[:stderr] = stderr.read unless aStdErr
|
62
|
+
result[:pid] = pid
|
63
|
+
end
|
64
|
+
end
|
65
|
+
ensure
|
66
|
+
Dir.chdir(orig_wd)
|
67
|
+
end
|
68
|
+
result[:exitcode] = (status && status.exitstatus) || 1
|
69
|
+
yield(result) if block_given?
|
70
|
+
raise ExecuteError.new(result) if result[:exitcode] != 0
|
71
|
+
return result
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.shell_out(aCommand,aWorkingDir=nil,aTimeout=nil,&block)
|
75
|
+
block_given? ? POpen4::shell(aCommand,aWorkingDir,aTimeout,STDOUT,STDERR,&block) : POpen4::shell(aCommand,aWorkingDir,aTimeout,STDOUT,STDERR)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Buzztools
|
2
|
+
module File
|
3
|
+
|
4
|
+
module_function
|
5
|
+
|
6
|
+
def sniff_seperator(aPath)
|
7
|
+
result = 0.upto(aPath.length-1) do |i|
|
8
|
+
char = aPath[i,1]
|
9
|
+
break char if char=='\\' || char=='/'
|
10
|
+
end
|
11
|
+
result = ::File::SEPARATOR if result==0
|
12
|
+
return result
|
13
|
+
end
|
14
|
+
|
15
|
+
def append_slash(aPath,aSep=nil)
|
16
|
+
aSep = sniff_seperator(aPath) unless aSep
|
17
|
+
aPath.ensure_suffix(aSep)
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove_slash(aPath)
|
21
|
+
last_char = aPath[-1,1]
|
22
|
+
aPath = aPath[0..-2] if last_char=='\\' || last_char=='/'
|
23
|
+
return aPath
|
24
|
+
end
|
25
|
+
|
26
|
+
#def ensure_prefix(aString,aPrefix)
|
27
|
+
# aString.begins_with?(aPrefix) ? aString : aPrefix+aString
|
28
|
+
#end
|
29
|
+
#
|
30
|
+
#def ensure_suffix(aString,aSuffix)
|
31
|
+
# aString.ends_with?(aSuffix) ? aString : aString+aSuffix
|
32
|
+
#end
|
33
|
+
|
34
|
+
# Remove base dir from given path. Result will be relative to base dir and not have a leading or trailing slash
|
35
|
+
#'/a/b/c','/a' = 'b/c'
|
36
|
+
#'/a/b/c','/' = 'a/b/c'
|
37
|
+
#'/','/' = ''
|
38
|
+
def path_debase(aPath,aBase)
|
39
|
+
aBase = append_slash(aBase)
|
40
|
+
aPath = remove_slash(aPath) unless aPath=='/'
|
41
|
+
aPath[0,aBase.length]==aBase ? aPath[aBase.length,aPath.length-aBase.length] : aPath
|
42
|
+
end
|
43
|
+
|
44
|
+
def path_rebase(aPath,aOldBase,aNewBase)
|
45
|
+
rel_path = path_debase(aPath,aOldBase)
|
46
|
+
append_slash(aNewBase)+rel_path
|
47
|
+
end
|
48
|
+
|
49
|
+
def path_combine(aBasePath,aPath)
|
50
|
+
return aBasePath if !aPath
|
51
|
+
return aPath if !aBasePath
|
52
|
+
return path_relative?(aPath) ? ::File.join(aBasePath,aPath) : aPath
|
53
|
+
end
|
54
|
+
|
55
|
+
# make path real according to file system
|
56
|
+
def real_path(aPath)
|
57
|
+
(path = Pathname.new(::File.expand_path(aPath))) && path.realpath.to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
# takes a path and combines it with a root path (which defaults to Dir.pwd) unless it is absolute
|
61
|
+
# the final result is then expanded
|
62
|
+
def canonize_path(aPath,aRootPath=nil)
|
63
|
+
path = path_combine(aRootPath,aPath)
|
64
|
+
path = real_path(path) if path
|
65
|
+
path
|
66
|
+
end
|
67
|
+
|
68
|
+
def find_upwards(aStartPath,aPath)
|
69
|
+
curr_path = ::File.expand_path(aStartPath)
|
70
|
+
while curr_path && !(test_path_exists = ::File.exists?(test_path = ::File.join(curr_path,aPath))) do
|
71
|
+
curr_path = path_parent(curr_path)
|
72
|
+
end
|
73
|
+
curr_path && test_path_exists ? test_path : nil
|
74
|
+
end
|
75
|
+
|
76
|
+
# allows special symbols in path
|
77
|
+
# currently only ... supported, which looks upward in the filesystem for the following relative path from the basepath
|
78
|
+
def expand_magic_path(aPath,aBasePath=nil)
|
79
|
+
aBasePath ||= Dir.pwd
|
80
|
+
path = aPath
|
81
|
+
if path.begins_with?('...')
|
82
|
+
rel_part = path.split3(/\.\.\.[\/\\]/)[2]
|
83
|
+
path = find_upwards(aBasePath,rel_part)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def path_parent(aPath)
|
88
|
+
return nil if is_root_path?(aPath)
|
89
|
+
append_slash(::File.dirname(remove_slash(expand_path(aPath))))
|
90
|
+
end
|
91
|
+
|
92
|
+
def simple_dir_name(aPath)
|
93
|
+
::File.basename(remove_slash(aPath))
|
94
|
+
end
|
95
|
+
|
96
|
+
def simple_file_name(aPath)
|
97
|
+
f = ::File.basename(aPath)
|
98
|
+
dot = f.index('.')
|
99
|
+
return dot ? f[0,dot] : f
|
100
|
+
end
|
101
|
+
|
102
|
+
def path_parts(aPath)
|
103
|
+
sep = sniff_seperator(aPath)
|
104
|
+
aPath.split(sep)
|
105
|
+
end
|
106
|
+
|
107
|
+
def extension(aFile,aExtended=true)
|
108
|
+
f = ::File.basename(aFile)
|
109
|
+
dot = aExtended ? f.index('.') : f.rindex('.')
|
110
|
+
return dot ? f[dot+1..-1] : f
|
111
|
+
end
|
112
|
+
|
113
|
+
def no_extension(aFile,aExtended=true)
|
114
|
+
ext = extension(aFile,aExtended)
|
115
|
+
return aFile.chomp('.'+ext)
|
116
|
+
end
|
117
|
+
|
118
|
+
def change_ext(aFile,aExt,aExtend=false)
|
119
|
+
no_extension(aFile,false)+(aExtend ? '.'+aExt+'.'+extension(aFile,false) : '.'+aExt)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
data/lib/buzztools/version.rb
CHANGED
@@ -0,0 +1,163 @@
|
|
1
|
+
# Versionary is a mixin for implementing versioning of models with ActiveRecord in Rails 3.2+
|
2
|
+
# It attempts to be better than other solutions eg.
|
3
|
+
#
|
4
|
+
# * no shadow tables
|
5
|
+
# * does not abuse ActiveRecord, no magic
|
6
|
+
# * no serialization at all, so queries work with all versions
|
7
|
+
# * associations work like normal ActiveRecord. The id column identifies a single version of an instance. You can associate to an old version
|
8
|
+
# * its easy to read and write old versions
|
9
|
+
# * if you use and keep updated the ver_current column, it is extremely fast
|
10
|
+
# * this file is really all there is to it
|
11
|
+
# * supports future versions that become current when the time comes eg. future price changes
|
12
|
+
#
|
13
|
+
# This is achieved by using the id column to identify each version of each instance, unlike some solutions that use the id per instance, then have to do trickery to provide versions.
|
14
|
+
# This means associations can simply attach to any version of any instance using the id column as normal
|
15
|
+
# Instances are identified by the iid column
|
16
|
+
#
|
17
|
+
# id iid version name price
|
18
|
+
# 1 3 1 eggs 2.99
|
19
|
+
# 2 3 2 eggs 3.10
|
20
|
+
# 3 4 1 bread 3.50
|
21
|
+
#
|
22
|
+
# In the above table, eggs (iid = 3) has 2 versions and bread (iid = 4) has 1 version.
|
23
|
+
# An order can simply attach to any product/version using the id column
|
24
|
+
#
|
25
|
+
# The eggs product would be created as follows :
|
26
|
+
#
|
27
|
+
# eggs = Product.create!(name: 'eggs', price: 2.99)
|
28
|
+
# eggs2 = eggs.create_version!(price: 3.10)
|
29
|
+
#
|
30
|
+
# current_10_dollar_products = Product.live_current_versions.where(price: 10)
|
31
|
+
#
|
32
|
+
# Migration
|
33
|
+
#
|
34
|
+
#class CreateThings < ActiveRecord::Migration
|
35
|
+
# def change
|
36
|
+
# create_table :things do |t|
|
37
|
+
# t.integer :iid # instance id - versions of an instance will have different ids but the same iid
|
38
|
+
# t.integer :version
|
39
|
+
# t.integer :current_from, limit: 8 # timestamp in milliseconds since 1970
|
40
|
+
# t.boolean :ver_current, null: false, default: false # optional, for performance. true indicates this is the current version
|
41
|
+
#
|
42
|
+
# t.integer :size
|
43
|
+
# t.string :colour
|
44
|
+
# t.string :shape
|
45
|
+
# end
|
46
|
+
# add_index(:things, [:iid, :version], :unique => true)
|
47
|
+
# end
|
48
|
+
#end
|
49
|
+
#
|
50
|
+
# Model
|
51
|
+
#
|
52
|
+
#class Thing < ActiveRecord::Base
|
53
|
+
#
|
54
|
+
# include Versionary
|
55
|
+
#
|
56
|
+
#end
|
57
|
+
#
|
58
|
+
#
|
59
|
+
module Versionary
|
60
|
+
|
61
|
+
def self.included(aClass)
|
62
|
+
aClass.class_eval do
|
63
|
+
|
64
|
+
def self.next_version_id(aIid)
|
65
|
+
where(iid: aIid).maximum(:version).to_i + 1
|
66
|
+
end
|
67
|
+
|
68
|
+
after_create do
|
69
|
+
updates = {}
|
70
|
+
updates[:iid] = id if !iid
|
71
|
+
if !version
|
72
|
+
updates[:version] = self.class.next_version_id(self.iid)
|
73
|
+
end
|
74
|
+
update_attributes!(updates) unless updates.empty?
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
# should be able to do eg. : TaxRate.where(owner_id: 1,dealership_id: 2).latest_versions.where(state: 'WA')
|
79
|
+
scope :live_latest_versions, -> {
|
80
|
+
inner = clone.select("iid, max(version) as version").group(:iid).to_sql
|
81
|
+
ids = ActiveRecord::Base.connection.execute("select id from (#{inner}) as v inner join #{table_name} as t on t.iid = v.iid and t.version = v.version").to_a
|
82
|
+
if (adapter = ActiveRecord::Base.configurations[Rails.env]['adapter'])=='postgresql'
|
83
|
+
ids = ids.map{|i| i['id']}.join(',')
|
84
|
+
elsif adapter.begins_with? 'mysql'
|
85
|
+
ids = ids.flatten.join(',')
|
86
|
+
else
|
87
|
+
raise "Adapter #{adapter} not supported"
|
88
|
+
end
|
89
|
+
|
90
|
+
#ids = ActiveRecord::Base.connection.execute("select id from (SELECT iid, max(version) as version FROM `things` GROUP BY iid) as v inner join things as t on t.iid = v.iid and t.version = v.version").to_a.flatten.join(',')
|
91
|
+
where "id IN (#{ids})"
|
92
|
+
}
|
93
|
+
|
94
|
+
# Scopes to the current version for all iids at the given timestamp
|
95
|
+
# This and other methods beginning with "live" do not use the ver_current column
|
96
|
+
scope :live_current_versions, ->(aTimestamp) {
|
97
|
+
inner = clone.select("iid, max(version) as version").where(["current_from <= ?",aTimestamp]).group(:iid).to_sql
|
98
|
+
ids = ActiveRecord::Base.connection.execute("select id from (#{inner}) as v inner join #{table_name} as t on t.iid = v.iid and t.version = v.version").to_a
|
99
|
+
if (adapter = ActiveRecord::Base.configurations[Rails.env]['adapter'])=='postgresql'
|
100
|
+
ids = ids.map{|i| i['id']}.join(',')
|
101
|
+
elsif adapter.begins_with? 'mysql'
|
102
|
+
ids = ids.flatten.join(',')
|
103
|
+
else
|
104
|
+
raise "Adapter #{adapter} not supported"
|
105
|
+
end
|
106
|
+
if ids.to_nil
|
107
|
+
where "id IN (#{ids})"
|
108
|
+
else
|
109
|
+
where("1=0") # relation that matches nothing
|
110
|
+
end
|
111
|
+
}
|
112
|
+
|
113
|
+
# Scopes to the current version of a given iid. Can only return 0 or 1 records
|
114
|
+
# This and other methods beginning with "live" do not use the ver_current column
|
115
|
+
scope :live_current_version, ->(aIid,aTimestamp=nil) {
|
116
|
+
aTimestamp ||= KojacUtils.timestamp
|
117
|
+
where(iid: aIid).where(["current_from <= ?",aTimestamp]).order('version DESC').limit(1)
|
118
|
+
}
|
119
|
+
|
120
|
+
# Scopes to current version for all iids using the ver_current column. The ver_current must be updated regularly using update_all_ver_current.
|
121
|
+
# This method is much faster than live_current_versions
|
122
|
+
scope :current_versions, -> {
|
123
|
+
where(ver_current: true)
|
124
|
+
}
|
125
|
+
|
126
|
+
# Updates the ver_current column, which enables simpler and much faster queries on current versions eg. using current_versions instead of live_current_versions.
|
127
|
+
# Must be run periodically eg. 4am daily
|
128
|
+
def self.update_all_ver_current
|
129
|
+
self.update_all(ver_current: false)
|
130
|
+
self.live_current_versions(Time.now.to_ms).update_all(ver_current: true)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def copyable_attributes
|
136
|
+
result = {}
|
137
|
+
self.class.columns.each do |c|
|
138
|
+
next if ['id', 'version'].include? c.name
|
139
|
+
result[c.name.to_sym] = self.send(c.name)
|
140
|
+
end
|
141
|
+
result
|
142
|
+
end
|
143
|
+
|
144
|
+
def new_version(aValues)
|
145
|
+
raise "iid must be set before calling new_version" unless self.iid
|
146
|
+
attrs = copyable_attributes
|
147
|
+
attrs[:version] = self.class.next_version_id(self.iid)
|
148
|
+
ver = self.class.new(attrs)
|
149
|
+
ver
|
150
|
+
end
|
151
|
+
|
152
|
+
def create_version!(aValues)
|
153
|
+
raise "iid must be set before calling new_version" unless self.iid
|
154
|
+
attrs = copyable_attributes
|
155
|
+
attrs[:version] = self.class.next_version_id(self.iid)
|
156
|
+
attrs.merge!(aValues.symbolize_keys)
|
157
|
+
self.class.create!(attrs)
|
158
|
+
end
|
159
|
+
|
160
|
+
def current_version
|
161
|
+
self.class.live_current_version(self.iid,KojacUtils.timestamp).first
|
162
|
+
end
|
163
|
+
end
|
metadata
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: buzztools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Gary McGhee
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2014-04-09 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: bundler
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
19
|
- - ! '>='
|
18
20
|
- !ruby/object:Gem::Version
|
@@ -20,6 +22,7 @@ dependencies:
|
|
20
22
|
type: :development
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
27
|
- - ! '>='
|
25
28
|
- !ruby/object:Gem::Version
|
@@ -27,6 +30,7 @@ dependencies:
|
|
27
30
|
- !ruby/object:Gem::Dependency
|
28
31
|
name: rake
|
29
32
|
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
30
34
|
requirements:
|
31
35
|
- - ! '>='
|
32
36
|
- !ruby/object:Gem::Version
|
@@ -34,6 +38,7 @@ dependencies:
|
|
34
38
|
type: :development
|
35
39
|
prerelease: false
|
36
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
37
42
|
requirements:
|
38
43
|
- - ! '>='
|
39
44
|
- !ruby/object:Gem::Version
|
@@ -52,6 +57,7 @@ files:
|
|
52
57
|
- Rakefile
|
53
58
|
- buzztools.gemspec
|
54
59
|
- lib/buzztools.rb
|
60
|
+
- lib/buzztools/config.rb
|
55
61
|
- lib/buzztools/extend_array.rb
|
56
62
|
- lib/buzztools/extend_bignum.rb
|
57
63
|
- lib/buzztools/extend_fixnum.rb
|
@@ -60,29 +66,39 @@ files:
|
|
60
66
|
- lib/buzztools/extend_object.rb
|
61
67
|
- lib/buzztools/extend_string.rb
|
62
68
|
- lib/buzztools/extend_time.rb
|
69
|
+
- lib/buzztools/extras/shell_extras.rb
|
70
|
+
- lib/buzztools/file.rb
|
63
71
|
- lib/buzztools/version.rb
|
72
|
+
- lib/buzztools/versionary.rb
|
64
73
|
homepage: https://github.com/buzzware/buzztools
|
65
74
|
licenses:
|
66
75
|
- MIT
|
67
|
-
metadata: {}
|
68
76
|
post_install_message:
|
69
77
|
rdoc_options: []
|
70
78
|
require_paths:
|
71
79
|
- lib
|
72
80
|
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
73
82
|
requirements:
|
74
83
|
- - ! '>='
|
75
84
|
- !ruby/object:Gem::Version
|
76
85
|
version: '0'
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
hash: -3067569873639998217
|
77
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
78
91
|
requirements:
|
79
92
|
- - ! '>='
|
80
93
|
- !ruby/object:Gem::Version
|
81
94
|
version: '0'
|
95
|
+
segments:
|
96
|
+
- 0
|
97
|
+
hash: -3067569873639998217
|
82
98
|
requirements: []
|
83
99
|
rubyforge_project:
|
84
|
-
rubygems_version:
|
100
|
+
rubygems_version: 1.8.23
|
85
101
|
signing_key:
|
86
|
-
specification_version:
|
102
|
+
specification_version: 3
|
87
103
|
summary: reusable function library
|
88
104
|
test_files: []
|
checksums.yaml
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
---
|
2
|
-
!binary "U0hBMQ==":
|
3
|
-
metadata.gz: !binary |-
|
4
|
-
NTI2MDkwZmY0Mjc1NTllMzk0NzgyMGY3YzBlNTAwNDlkNWRlOTJkNA==
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
OTg3MWE5YTQ3ODU3NGU1YmU4YjdkNGVjYzA1NGU0M2VjZDRhMDgxNA==
|
7
|
-
SHA512:
|
8
|
-
metadata.gz: !binary |-
|
9
|
-
MDhiZTMwNmQyYWVjNmIwZDE2NjY5OTMwZmQ5NjNmNTcxZmEyOTg0YTYxZDE3
|
10
|
-
NzA4MDY3MDJiMDM0ZjkzMDYzNGVhYzhhYmM1MzlhOTlhNzQ1N2FjN2RlZjI2
|
11
|
-
ODQxYTVkMzQwMDI2YTg3ZTAxZTBkYTU1YTFkNmQ4Y2MzMDY4YzM=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
OGZmYjZiMDllNmVkMDlmMTY0YzQ3YTFlNmJhYTQyOGU1NDYxZTZiOGZhYTJk
|
14
|
-
MDlmNDNmNjVmOTdiMzY0NjI0ZDA2ZmVmYTM1NTg2ZDM1ZDM0MTJjZTEwOTZh
|
15
|
-
YTM0MjFmMjY3ODkwYjU0NjY1OTUzMTgzNDFkMjVkZGY5ZTk2MTY=
|