friendly 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/.document +5 -0
  2. data/.gitignore +23 -0
  3. data/APACHE-LICENSE +202 -0
  4. data/LICENSE +20 -0
  5. data/README.md +173 -0
  6. data/Rakefile +67 -0
  7. data/VERSION +1 -0
  8. data/examples/friendly.yml +7 -0
  9. data/friendly.gemspec +201 -0
  10. data/lib/friendly/attribute.rb +65 -0
  11. data/lib/friendly/boolean.rb +6 -0
  12. data/lib/friendly/cache/by_id.rb +33 -0
  13. data/lib/friendly/cache.rb +24 -0
  14. data/lib/friendly/config.rb +5 -0
  15. data/lib/friendly/data_store.rb +72 -0
  16. data/lib/friendly/document.rb +165 -0
  17. data/lib/friendly/document_table.rb +56 -0
  18. data/lib/friendly/index.rb +73 -0
  19. data/lib/friendly/memcached.rb +48 -0
  20. data/lib/friendly/newrelic.rb +6 -0
  21. data/lib/friendly/query.rb +42 -0
  22. data/lib/friendly/sequel_monkey_patches.rb +35 -0
  23. data/lib/friendly/storage.rb +31 -0
  24. data/lib/friendly/storage_factory.rb +24 -0
  25. data/lib/friendly/storage_proxy.rb +103 -0
  26. data/lib/friendly/table.rb +15 -0
  27. data/lib/friendly/table_creator.rb +43 -0
  28. data/lib/friendly/time.rb +14 -0
  29. data/lib/friendly/translator.rb +32 -0
  30. data/lib/friendly/uuid.rb +143 -0
  31. data/lib/friendly.rb +49 -0
  32. data/rails/init.rb +3 -0
  33. data/spec/fakes/data_store_fake.rb +29 -0
  34. data/spec/fakes/database_fake.rb +12 -0
  35. data/spec/fakes/dataset_fake.rb +28 -0
  36. data/spec/fakes/document.rb +18 -0
  37. data/spec/fakes/serializer_fake.rb +12 -0
  38. data/spec/fakes/time_fake.rb +12 -0
  39. data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
  40. data/spec/integration/batch_insertion_spec.rb +29 -0
  41. data/spec/integration/convenience_api_spec.rb +25 -0
  42. data/spec/integration/count_spec.rb +12 -0
  43. data/spec/integration/default_value_spec.rb +15 -0
  44. data/spec/integration/find_via_cache_spec.rb +101 -0
  45. data/spec/integration/finder_spec.rb +64 -0
  46. data/spec/integration/index_spec.rb +57 -0
  47. data/spec/integration/pagination_spec.rb +63 -0
  48. data/spec/integration/table_creator_spec.rb +52 -0
  49. data/spec/integration/write_through_cache_spec.rb +53 -0
  50. data/spec/spec.opts +1 -0
  51. data/spec/spec_helper.rb +90 -0
  52. data/spec/unit/attribute_spec.rb +64 -0
  53. data/spec/unit/cache_by_id_spec.rb +102 -0
  54. data/spec/unit/cache_spec.rb +21 -0
  55. data/spec/unit/config_spec.rb +4 -0
  56. data/spec/unit/data_store_spec.rb +188 -0
  57. data/spec/unit/document_spec.rb +311 -0
  58. data/spec/unit/document_table_spec.rb +126 -0
  59. data/spec/unit/friendly_spec.rb +25 -0
  60. data/spec/unit/index_spec.rb +196 -0
  61. data/spec/unit/memcached_spec.rb +114 -0
  62. data/spec/unit/query_spec.rb +104 -0
  63. data/spec/unit/storage_factory_spec.rb +59 -0
  64. data/spec/unit/storage_proxy_spec.rb +218 -0
  65. data/spec/unit/translator_spec.rb +96 -0
  66. data/website/index.html +210 -0
  67. data/website/scripts/clipboard.swf +0 -0
  68. data/website/scripts/shBrushAS3.js +61 -0
  69. data/website/scripts/shBrushBash.js +66 -0
  70. data/website/scripts/shBrushCSharp.js +67 -0
  71. data/website/scripts/shBrushColdFusion.js +102 -0
  72. data/website/scripts/shBrushCpp.js +99 -0
  73. data/website/scripts/shBrushCss.js +93 -0
  74. data/website/scripts/shBrushDelphi.js +57 -0
  75. data/website/scripts/shBrushDiff.js +43 -0
  76. data/website/scripts/shBrushErlang.js +54 -0
  77. data/website/scripts/shBrushGroovy.js +69 -0
  78. data/website/scripts/shBrushJScript.js +52 -0
  79. data/website/scripts/shBrushJava.js +59 -0
  80. data/website/scripts/shBrushJavaFX.js +60 -0
  81. data/website/scripts/shBrushPerl.js +74 -0
  82. data/website/scripts/shBrushPhp.js +91 -0
  83. data/website/scripts/shBrushPlain.js +35 -0
  84. data/website/scripts/shBrushPowerShell.js +76 -0
  85. data/website/scripts/shBrushPython.js +66 -0
  86. data/website/scripts/shBrushRuby.js +57 -0
  87. data/website/scripts/shBrushScala.js +53 -0
  88. data/website/scripts/shBrushSql.js +68 -0
  89. data/website/scripts/shBrushVb.js +58 -0
  90. data/website/scripts/shBrushXml.js +71 -0
  91. data/website/scripts/shCore.js +30 -0
  92. data/website/scripts/shLegacy.js +30 -0
  93. data/website/styles/friendly.css +103 -0
  94. data/website/styles/help.png +0 -0
  95. data/website/styles/ie.css +35 -0
  96. data/website/styles/magnifier.png +0 -0
  97. data/website/styles/page_white_code.png +0 -0
  98. data/website/styles/page_white_copy.png +0 -0
  99. data/website/styles/print.css +29 -0
  100. data/website/styles/printer.png +0 -0
  101. data/website/styles/screen.css +257 -0
  102. data/website/styles/shCore.css +330 -0
  103. data/website/styles/shThemeDefault.css +173 -0
  104. data/website/styles/shThemeDjango.css +176 -0
  105. data/website/styles/shThemeEclipse.css +190 -0
  106. data/website/styles/shThemeEmacs.css +175 -0
  107. data/website/styles/shThemeFadeToGrey.css +177 -0
  108. data/website/styles/shThemeMidnight.css +175 -0
  109. data/website/styles/shThemeRDark.css +175 -0
  110. metadata +264 -0
@@ -0,0 +1,73 @@
1
+ require 'friendly/table'
2
+
3
+ module Friendly
4
+ class Index < Table
5
+ attr_reader :klass, :fields, :datastore
6
+
7
+ def initialize(klass, fields, datastore = Friendly.datastore)
8
+ @klass = klass
9
+ @fields = fields
10
+ @datastore = datastore
11
+ end
12
+
13
+ def table_name
14
+ ["index", klass.table_name, "on", fields.join("_and_")].join("_")
15
+ end
16
+
17
+ def satisfies?(query)
18
+ exact_match?(query) || valid_partial_match?(query)
19
+ end
20
+
21
+ def first(query)
22
+ row = datastore.first(self, query)
23
+ row && klass.first(:id => row[:id])
24
+ end
25
+
26
+ def all(query)
27
+ ids = datastore.all(self, query).map { |row| row[:id] }
28
+ klass.all(:id => ids, :preserve_order! => !query.order.nil?)
29
+ end
30
+
31
+ def count(query)
32
+ datastore.count(self, query)
33
+ end
34
+
35
+ def create(document)
36
+ datastore.insert(self, record(document))
37
+ end
38
+
39
+ def update(document)
40
+ datastore.update(self, document.id, record(document))
41
+ end
42
+
43
+ def destroy(document)
44
+ datastore.delete(self, document.id)
45
+ end
46
+
47
+ protected
48
+ def exact_match?(query)
49
+ query.conditions.keys.map { |f| f.to_s }.sort ==
50
+ fields.map { |f| f.to_s }.sort &&
51
+ valid_order?(query.order)
52
+ end
53
+
54
+ def valid_partial_match?(query)
55
+ condition_fields = query.conditions.keys
56
+ sorted = condition_fields.sort { |a,b| field_index(a) <=> field_index(b) }
57
+ sorted << query.order.expression if query.order
58
+ sorted.zip(fields).all? { |a,b| a == b }
59
+ end
60
+
61
+ def valid_order?(order)
62
+ order.nil? || order.expression == fields.last
63
+ end
64
+
65
+ def field_index(attr)
66
+ fields.index(attr) || 0
67
+ end
68
+
69
+ def record(document)
70
+ Hash[*(fields + [:id]).map { |f| [f, document.send(f)] }.flatten]
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,48 @@
1
+ module Friendly
2
+ class Memcached
3
+ attr_reader :cache
4
+
5
+ def initialize(cache)
6
+ @cache = cache
7
+ end
8
+
9
+ def set(key, value)
10
+ @cache.set(key, value)
11
+ end
12
+
13
+ def get(key)
14
+ @cache.get(key)
15
+ rescue ::Memcached::NotFound
16
+ if block_given?
17
+ miss(key) { yield }
18
+ end
19
+ end
20
+
21
+ def multiget(keys)
22
+ return {} if keys.empty?
23
+
24
+ hits = @cache.get(keys)
25
+ missing_keys = keys - hits.keys
26
+
27
+ if !missing_keys.empty? && block_given?
28
+ missing_keys.each do |missing_key|
29
+ hits.merge!(missing_key => miss(missing_key) { yield(missing_key) })
30
+ end
31
+ end
32
+
33
+ hits
34
+ end
35
+
36
+ def delete(key)
37
+ cache.delete(key)
38
+ rescue ::Memcached::NotFound
39
+ end
40
+
41
+ protected
42
+ def miss(key)
43
+ value = yield
44
+ @cache.set(key, value)
45
+ value
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,6 @@
1
+ Friendly::Memcached.class_eval do
2
+ add_method_tracer :miss, "Friendly::Memcached/miss"
3
+ add_method_tracer :get, "Friendly::Memcached/get"
4
+ add_method_tracer :multiget, "Friendly::Memcached/multiget"
5
+ end
6
+
@@ -0,0 +1,42 @@
1
+ module Friendly
2
+ class Query
3
+ attr_reader :conditions, :limit, :order,
4
+ :preserve_order, :offset, :uuid_klass,
5
+ :page, :per_page
6
+
7
+ def initialize(parameters, uuid_klass = UUID)
8
+ @uuid_klass = uuid_klass
9
+ @conditions = parameters.reject { |k,v| k.to_s =~ /!$/ }
10
+ @page = (parameters[:page!] || 1).to_i
11
+
12
+ [:per_page!, :limit!, :offset!, :order!, :preserve_order!].each do |p|
13
+ instance_variable_set("@#{p.to_s.gsub(/!/, '')}", parameters[p])
14
+ end
15
+
16
+ handle_pagination if per_page
17
+ convert_ids_to_uuids
18
+ end
19
+
20
+ def preserve_order?
21
+ preserve_order
22
+ end
23
+
24
+ def offset?
25
+ offset
26
+ end
27
+
28
+ protected
29
+ def convert_ids_to_uuids
30
+ if conditions[:id] && conditions[:id].is_a?(Array)
31
+ conditions[:id] = conditions[:id].map { |i| uuid_klass.new(i) }
32
+ elsif conditions[:id]
33
+ conditions[:id] = uuid_klass.new(conditions[:id])
34
+ end
35
+ end
36
+
37
+ def handle_pagination
38
+ @limit = per_page
39
+ @offset = (page - 1) * per_page
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,35 @@
1
+ require 'sequel'
2
+
3
+ # Out of the box, Sequel uses IS TRUE/FALSE for boolean parameters
4
+ # This prevents MySQL from using indexes.
5
+ #
6
+ # This patch fixes that.
7
+ module Sequel
8
+ module SQL
9
+ class BooleanExpression
10
+ def self.from_value_pairs(pairs, op=:AND, negate=false)
11
+ pairs = pairs.collect do |l,r|
12
+ ce = case r
13
+ when Range
14
+ new(:AND, new(:>=, l, r.begin), new(r.exclude_end? ? :< : :<=, l, r.end))
15
+ when Array, ::Sequel::Dataset, SQLArray
16
+ new(:IN, l, r)
17
+ when NegativeBooleanConstant
18
+ new(:"IS NOT", l, r.constant)
19
+ when BooleanConstant
20
+ new(:IS, l, r.constant)
21
+ when NilClass
22
+ new(:IS, l, r)
23
+ when Regexp
24
+ StringExpression.like(l, r)
25
+ else
26
+ new(:'=', l, r)
27
+ end
28
+ negate ? invert(ce) : ce
29
+ end
30
+ pairs.length == 1 ? pairs.at(0) : new(op, *pairs)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,31 @@
1
+ module Friendly
2
+ class Storage
3
+ def create(document)
4
+ raise NotImplementedError, "#{self.class.name}#create is not implemented."
5
+ end
6
+
7
+ def update(document)
8
+ raise NotImplementedError, "#{self.class.name}#update is not implemented."
9
+ end
10
+
11
+ def destroy(document)
12
+ raise NotImplementedError, "#{self.class.name}#destroy is not implemented."
13
+ end
14
+
15
+ def first(conditions)
16
+ raise NotImplementedError, "#{self.class.name}#first is not implemented."
17
+ end
18
+
19
+ def all(conditions)
20
+ raise NotImplementedError, "#{self.class.name}#all is not implemented."
21
+ end
22
+
23
+ def count(query)
24
+ raise NotImplementedError, "#{self.class.name}#count is not implemented."
25
+ end
26
+
27
+ def satisfies?(conditions)
28
+ raise NotImplementedError, "#{self.class.name}#satisfies? is not implemented."
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ module Friendly
2
+ class StorageFactory
3
+ attr_reader :table_klass, :index_klass, :cache_klass
4
+
5
+ def initialize(table_klass = DocumentTable, index_klass = Index,
6
+ cache_klass = Cache)
7
+ @table_klass = table_klass
8
+ @index_klass = index_klass
9
+ @cache_klass = cache_klass
10
+ end
11
+
12
+ def document_table(*args)
13
+ table_klass.new(*args)
14
+ end
15
+
16
+ def index(*args)
17
+ index_klass.new(*args)
18
+ end
19
+
20
+ def cache(*args)
21
+ cache_klass.cache_for(*args)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,103 @@
1
+ require 'friendly/storage_factory'
2
+ require 'friendly/table_creator'
3
+
4
+ module Friendly
5
+ class StorageProxy
6
+ attr_reader :klass, :storage_factory, :tables, :table_creator, :caches
7
+
8
+ def initialize(klass, storage_factory = StorageFactory.new,
9
+ table_creator=TableCreator.new)
10
+ super()
11
+ @klass = klass
12
+ @storage_factory = storage_factory
13
+ @table_creator = table_creator
14
+ @tables = [storage_factory.document_table(klass)]
15
+ @caches = []
16
+ end
17
+
18
+ def first(conditions)
19
+ first_from_cache(conditions) do
20
+ index_for(conditions).first(conditions)
21
+ end
22
+ end
23
+
24
+ def all(query)
25
+ objects = perform_all(query).compact
26
+ if query.preserve_order?
27
+ order = query.conditions[:id]
28
+ objects.sort { |a,b| order.index(a.id) <=> order.index(b.id) }
29
+ else
30
+ objects
31
+ end
32
+ end
33
+
34
+ def count(query)
35
+ index_for(query).count(query)
36
+ end
37
+
38
+ def add(*args)
39
+ tables << storage_factory.index(klass, *args)
40
+ end
41
+
42
+ def cache(fields, options = {})
43
+ caches << storage_factory.cache(klass, fields, options)
44
+ end
45
+
46
+ def create(document)
47
+ each_store { |s| s.create(document) }
48
+ end
49
+
50
+ def update(document)
51
+ each_store { |s| s.update(document) }
52
+ end
53
+
54
+ def destroy(document)
55
+ stores.reverse.each { |i| i.destroy(document) }
56
+ end
57
+
58
+ def create_tables!
59
+ tables.each { |t| table_creator.create(t) }
60
+ end
61
+
62
+ def index_for(conditions)
63
+ index = tables.detect { |i| i.satisfies?(conditions) }
64
+ if index.nil?
65
+ raise MissingIndex, "No index found to satisfy: #{conditions.inspect}."
66
+ end
67
+ index
68
+ end
69
+
70
+ protected
71
+ def each_store
72
+ stores.each { |s| yield(s) }
73
+ end
74
+
75
+ def stores
76
+ tables + caches
77
+ end
78
+
79
+ def first_from_cache(query)
80
+ cache = cache_for(query)
81
+ if cache
82
+ cache.first(query) { yield }
83
+ else
84
+ yield
85
+ end
86
+ end
87
+
88
+ def cache_for(query)
89
+ caches.detect { |c| c.satisfies?(query) }
90
+ end
91
+
92
+ def perform_all(query)
93
+ cache = cache_for(query)
94
+ if cache
95
+ cache.all(query) do |missing_key|
96
+ index_for(query).first(Query.new(:id => missing_key.split("/").last))
97
+ end
98
+ else
99
+ index_for(query).all(query)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,15 @@
1
+ require 'friendly/storage'
2
+ module Friendly
3
+ class Table < Storage
4
+ attr_reader :datastore
5
+
6
+ def initialize(datastore)
7
+ @datastore = datastore
8
+ end
9
+
10
+ def table_name
11
+ raise NotImplementedError, "#{self.class.name}#table_name is not implemented."
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,43 @@
1
+ module Friendly
2
+ class TableCreator
3
+ attr_reader :db
4
+
5
+ def initialize(db = Friendly.db)
6
+ @db = db
7
+ end
8
+
9
+ def create(table)
10
+ unless db.table_exists?(table.table_name)
11
+ case table
12
+ when DocumentTable
13
+ create_document_table(table)
14
+ when Index
15
+ create_index_table(table)
16
+ end
17
+ end
18
+ end
19
+
20
+ protected
21
+ def create_document_table(table)
22
+ db.create_table(table.table_name) do
23
+ primary_key :added_id
24
+ binary :id, :size => 16
25
+ String :attributes, :text => true
26
+ Time :created_at
27
+ Time :updated_at
28
+ end
29
+ end
30
+
31
+ def create_index_table(table)
32
+ db.create_table(table.table_name) do
33
+ binary :id, :size => 16
34
+ table.fields.flatten.each do |f|
35
+ method(table.klass.attributes[f].type.name.to_sym).call(f)
36
+ end
37
+ primary_key table.fields.flatten + [:id]
38
+ unique :id
39
+ end
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,14 @@
1
+ # This class was extracted from the cassandra gem by Evan Weaver
2
+ # As such, it is distributed under the terms of the apache license.
3
+ # See the APACHE-LICENSE file in the root of this project for more information.
4
+ #
5
+ class Time
6
+ def self.stamp
7
+ Time.now.stamp
8
+ end
9
+
10
+ def stamp
11
+ to_i * 1_000_000 + usec
12
+ end
13
+ end
14
+
@@ -0,0 +1,32 @@
1
+ module Friendly
2
+ class Translator
3
+ RESERVED_ATTRS = [:id, :created_at, :updated_at].freeze
4
+
5
+ attr_reader :serializer, :time
6
+
7
+ def initialize(serializer = JSON, time = Time)
8
+ @serializer = serializer
9
+ @time = time
10
+ end
11
+
12
+ def to_object(klass, record)
13
+ record.delete(:added_id)
14
+ attributes = serializer.parse(record.delete(:attributes))
15
+ klass.new attributes.merge(record).merge(:new_record => false)
16
+ end
17
+
18
+ def to_record(document)
19
+ { :id => document.id,
20
+ :created_at => document.created_at,
21
+ :updated_at => time.new,
22
+ :attributes => serialize(document) }
23
+ end
24
+
25
+ protected
26
+ def serialize(document)
27
+ attrs = document.to_hash.reject { |k,v| RESERVED_ATTRS.include?(k) }
28
+ serializer.generate(attrs)
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,143 @@
1
+ require 'friendly/time'
2
+ # This class was extracted from the cassandra gem by Evan Weaver
3
+ # As such, it is distributed under the terms of the apache license.
4
+ # See the APACHE-LICENSE file in the root of this project for more information.
5
+ #
6
+ module Friendly
7
+ # UUID format version 1, as specified in RFC 4122, with jitter in place of the mac address and sequence counter.
8
+ class UUID
9
+
10
+ class InvalidVersion < StandardError; end
11
+ class TypeError < ::TypeError; end
12
+
13
+ GREGORIAN_EPOCH_OFFSET = 0x01B2_1DD2_1381_4000 # Oct 15, 1582
14
+
15
+ VARIANT = 0b1000_0000_0000_0000
16
+
17
+ def initialize(bytes = nil)
18
+ case bytes
19
+ when self.class # UUID
20
+ @bytes = bytes.to_s
21
+ when String
22
+ case bytes.size
23
+ when 16 # Raw byte array
24
+ @bytes = bytes
25
+ when 36 # Human-readable UUID representation; inverse of #to_guid
26
+ elements = bytes.split("-")
27
+ raise TypeError, "Expected #{bytes.inspect} to cast to a #{self.class} (malformed UUID representation)" if elements.size != 5
28
+ @bytes = elements.join.to_a.pack('H32')
29
+ else
30
+ raise TypeError, "Expected #{bytes.inspect} to cast to a #{self.class} (invalid bytecount)"
31
+ end
32
+
33
+ when Integer
34
+ raise TypeError, "Expected #{bytes.inspect} to cast to a #{self.class} (integer out of range)" if bytes < 0 or bytes > 2**128
35
+ @bytes = [
36
+ (bytes >> 96) & 0xFFFF_FFFF,
37
+ (bytes >> 64) & 0xFFFF_FFFF,
38
+ (bytes >> 32) & 0xFFFF_FFFF,
39
+ bytes & 0xFFFF_FFFF
40
+ ].pack("NNNN")
41
+
42
+ when NilClass, Time
43
+ time = (bytes || Time).stamp * 10 + GREGORIAN_EPOCH_OFFSET
44
+ # See http://github.com/spectra/ruby-uuid/
45
+ @bytes = [
46
+ time & 0xFFFF_FFFF,
47
+ time >> 32,
48
+ ((time >> 48) & 0x0FFF) | 0x1000,
49
+ # Top 3 bytes reserved
50
+ rand(2**13) | VARIANT,
51
+ rand(2**16),
52
+ rand(2**32)
53
+ ].pack("NnnnnN")
54
+
55
+ else
56
+ raise TypeError, "Expected #{bytes.inspect} to cast to a #{self.class} (unknown source class)"
57
+ end
58
+ end
59
+
60
+ def to_i
61
+ ints = @bytes.unpack("NNNN")
62
+ (ints[0] << 96) +
63
+ (ints[1] << 64) +
64
+ (ints[2] << 32) +
65
+ ints[3]
66
+ end
67
+
68
+ def version
69
+ time_high = @bytes.unpack("NnnQ")[2]
70
+ version = (time_high & 0xF000).to_s(16)[0].chr.to_i
71
+ version > 0 and version < 6 ? version : -1
72
+ end
73
+
74
+ def variant
75
+ @bytes.unpack('QnnN')[1] >> 13
76
+ end
77
+
78
+ def to_guid
79
+ elements = @bytes.unpack("NnnCCa6")
80
+ node = elements[-1].unpack('C*')
81
+ elements[-1] = '%02x%02x%02x%02x%02x%02x' % node
82
+ "%08x-%04x-%04x-%02x%02x-%s" % elements
83
+ end
84
+
85
+ def to_json(*args)
86
+ to_guid.to_json(*args)
87
+ end
88
+
89
+ def seconds
90
+ total_usecs / 1_000_000
91
+ end
92
+
93
+ def usecs
94
+ total_usecs % 1_000_000
95
+ end
96
+
97
+ def <=>(other)
98
+ total_usecs <=> other.send(:total_usecs)
99
+ end
100
+
101
+ def inspect(long = false)
102
+ "<Friendly::UUID##{object_id} time: #{
103
+ Time.at(seconds).inspect
104
+ }, usecs: #{
105
+ usecs
106
+ } jitter: #{
107
+ @bytes.unpack('QQ')[1]
108
+ }" + (long ? ", version: #{version}, variant: #{variant}, guid: #{to_guid}>" : ">")
109
+ end
110
+
111
+ def <=>(other)
112
+ self.to_i <=> other.to_i
113
+ end
114
+
115
+ def hash
116
+ @bytes.hash
117
+ end
118
+
119
+ def eql?(other)
120
+ other.is_a?(Comparable) and @bytes == other.to_s
121
+ end
122
+
123
+ def ==(other)
124
+ self.to_s == other.to_s
125
+ end
126
+
127
+ def to_s
128
+ @bytes
129
+ end
130
+
131
+ def sql_literal(dataset)
132
+ dataset.literal(to_s)
133
+ end
134
+
135
+ private
136
+
137
+ def total_usecs
138
+ elements = @bytes.unpack("NnnQ")
139
+ (elements[0] + (elements[1] << 32) + ((elements[2] & 0x0FFF) << 48) - GREGORIAN_EPOCH_OFFSET) / 10
140
+ end
141
+ end
142
+ end
143
+
data/lib/friendly.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'friendly/attribute'
2
+ require 'friendly/boolean'
3
+ require 'friendly/cache'
4
+ require 'friendly/cache/by_id'
5
+ require 'friendly/config'
6
+ require 'friendly/data_store'
7
+ require 'friendly/document'
8
+ require 'friendly/document_table'
9
+ require 'friendly/index'
10
+ require 'friendly/memcached'
11
+ require 'friendly/query'
12
+ require 'friendly/sequel_monkey_patches'
13
+ require 'friendly/storage_factory'
14
+ require 'friendly/storage_proxy'
15
+ require 'friendly/translator'
16
+ require 'friendly/uuid'
17
+
18
+ require 'will_paginate/collection'
19
+
20
+ module Friendly
21
+ class << self
22
+ attr_accessor :datastore, :db, :cache
23
+
24
+ def configure(config)
25
+ self.db = Sequel.connect(config)
26
+ self.datastore = DataStore.new(db)
27
+ end
28
+
29
+ def batch
30
+ begin
31
+ datastore.start_batch
32
+ yield
33
+ datastore.flush_batch
34
+ ensure
35
+ datastore.reset_batch
36
+ end
37
+ end
38
+
39
+ def create_tables!
40
+ Document.create_tables!
41
+ end
42
+ end
43
+
44
+ class Error < RuntimeError; end
45
+ class RecordNotFound < Error; end
46
+ class MissingIndex < Error; end
47
+ class NoConverterExists < Friendly::Error; end
48
+ class NotSupported < Friendly::Error; end
49
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ config = YAML.load(File.read(RAILS_ROOT + "/config/friendly.yml"))[RAILS_ENV]
2
+ Friendly.configure(config)
3
+
@@ -0,0 +1,29 @@
1
+ class DataStoreFake
2
+ attr_writer :insert, :all, :first
3
+ attr_reader :inserts, :updates
4
+
5
+ def initialize(opts = {})
6
+ opts.each { |k,v| send("#{k}=", v) }
7
+ @inserts = []
8
+ @updates = []
9
+ end
10
+
11
+ def insert(*args)
12
+ @inserts << args
13
+ @insert
14
+ end
15
+
16
+ def update(*args)
17
+ @updates << args
18
+ @update
19
+ end
20
+
21
+ def all(*args)
22
+ @all[args]
23
+ end
24
+
25
+ def first(*args)
26
+ @first[args]
27
+ end
28
+ end
29
+