friendly_postgres 0.4.3

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.
Files changed (129) hide show
  1. data/.document +2 -0
  2. data/.gitignore +26 -0
  3. data/APACHE-LICENSE +202 -0
  4. data/CHANGELOG.md +19 -0
  5. data/CONTRIBUTORS.md +7 -0
  6. data/LICENSE +20 -0
  7. data/README.md +265 -0
  8. data/Rakefile +68 -0
  9. data/TODO.md +5 -0
  10. data/VERSION +1 -0
  11. data/examples/friendly.yml +7 -0
  12. data/friendly.gemspec +232 -0
  13. data/lib/friendly.rb +54 -0
  14. data/lib/friendly/associations.rb +7 -0
  15. data/lib/friendly/associations/association.rb +34 -0
  16. data/lib/friendly/associations/set.rb +37 -0
  17. data/lib/friendly/attribute.rb +91 -0
  18. data/lib/friendly/boolean.rb +10 -0
  19. data/lib/friendly/cache.rb +24 -0
  20. data/lib/friendly/cache/by_id.rb +33 -0
  21. data/lib/friendly/config.rb +5 -0
  22. data/lib/friendly/data_store.rb +72 -0
  23. data/lib/friendly/document.rb +257 -0
  24. data/lib/friendly/document_table.rb +56 -0
  25. data/lib/friendly/index.rb +73 -0
  26. data/lib/friendly/memcached.rb +48 -0
  27. data/lib/friendly/named_scope.rb +17 -0
  28. data/lib/friendly/newrelic.rb +6 -0
  29. data/lib/friendly/query.rb +42 -0
  30. data/lib/friendly/scope.rb +100 -0
  31. data/lib/friendly/scope_proxy.rb +45 -0
  32. data/lib/friendly/sequel_monkey_patches.rb +34 -0
  33. data/lib/friendly/storage.rb +31 -0
  34. data/lib/friendly/storage_factory.rb +24 -0
  35. data/lib/friendly/storage_proxy.rb +103 -0
  36. data/lib/friendly/table.rb +15 -0
  37. data/lib/friendly/table_creator.rb +48 -0
  38. data/lib/friendly/time.rb +14 -0
  39. data/lib/friendly/translator.rb +32 -0
  40. data/lib/friendly/uuid.rb +148 -0
  41. data/rails/init.rb +3 -0
  42. data/spec/config.yml.example +7 -0
  43. data/spec/fakes/data_store_fake.rb +29 -0
  44. data/spec/fakes/database_fake.rb +12 -0
  45. data/spec/fakes/dataset_fake.rb +28 -0
  46. data/spec/fakes/document.rb +18 -0
  47. data/spec/fakes/serializer_fake.rb +12 -0
  48. data/spec/fakes/time_fake.rb +12 -0
  49. data/spec/integration/ad_hoc_scopes_spec.rb +42 -0
  50. data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
  51. data/spec/integration/batch_insertion_spec.rb +29 -0
  52. data/spec/integration/convenience_api_spec.rb +25 -0
  53. data/spec/integration/count_spec.rb +12 -0
  54. data/spec/integration/default_value_spec.rb +15 -0
  55. data/spec/integration/find_via_cache_spec.rb +101 -0
  56. data/spec/integration/finder_spec.rb +64 -0
  57. data/spec/integration/has_many_spec.rb +18 -0
  58. data/spec/integration/index_spec.rb +57 -0
  59. data/spec/integration/named_scope_spec.rb +34 -0
  60. data/spec/integration/pagination_spec.rb +63 -0
  61. data/spec/integration/scope_chaining_spec.rb +22 -0
  62. data/spec/integration/table_creator_spec.rb +64 -0
  63. data/spec/integration/write_through_cache_spec.rb +53 -0
  64. data/spec/spec.opts +1 -0
  65. data/spec/spec_helper.rb +103 -0
  66. data/spec/unit/associations/association_spec.rb +57 -0
  67. data/spec/unit/associations/set_spec.rb +43 -0
  68. data/spec/unit/attribute_spec.rb +105 -0
  69. data/spec/unit/cache_by_id_spec.rb +102 -0
  70. data/spec/unit/cache_spec.rb +21 -0
  71. data/spec/unit/config_spec.rb +4 -0
  72. data/spec/unit/data_store_spec.rb +188 -0
  73. data/spec/unit/document_spec.rb +358 -0
  74. data/spec/unit/document_table_spec.rb +126 -0
  75. data/spec/unit/friendly_spec.rb +25 -0
  76. data/spec/unit/index_spec.rb +196 -0
  77. data/spec/unit/memcached_spec.rb +114 -0
  78. data/spec/unit/named_scope_spec.rb +16 -0
  79. data/spec/unit/query_spec.rb +104 -0
  80. data/spec/unit/scope_proxy_spec.rb +44 -0
  81. data/spec/unit/scope_spec.rb +113 -0
  82. data/spec/unit/storage_factory_spec.rb +59 -0
  83. data/spec/unit/storage_proxy_spec.rb +218 -0
  84. data/spec/unit/translator_spec.rb +96 -0
  85. data/website/index.html +210 -0
  86. data/website/scripts/clipboard.swf +0 -0
  87. data/website/scripts/shBrushAS3.js +61 -0
  88. data/website/scripts/shBrushBash.js +66 -0
  89. data/website/scripts/shBrushCSharp.js +67 -0
  90. data/website/scripts/shBrushColdFusion.js +102 -0
  91. data/website/scripts/shBrushCpp.js +99 -0
  92. data/website/scripts/shBrushCss.js +93 -0
  93. data/website/scripts/shBrushDelphi.js +57 -0
  94. data/website/scripts/shBrushDiff.js +43 -0
  95. data/website/scripts/shBrushErlang.js +54 -0
  96. data/website/scripts/shBrushGroovy.js +69 -0
  97. data/website/scripts/shBrushJScript.js +52 -0
  98. data/website/scripts/shBrushJava.js +59 -0
  99. data/website/scripts/shBrushJavaFX.js +60 -0
  100. data/website/scripts/shBrushPerl.js +74 -0
  101. data/website/scripts/shBrushPhp.js +91 -0
  102. data/website/scripts/shBrushPlain.js +35 -0
  103. data/website/scripts/shBrushPowerShell.js +76 -0
  104. data/website/scripts/shBrushPython.js +66 -0
  105. data/website/scripts/shBrushRuby.js +57 -0
  106. data/website/scripts/shBrushScala.js +53 -0
  107. data/website/scripts/shBrushSql.js +68 -0
  108. data/website/scripts/shBrushVb.js +58 -0
  109. data/website/scripts/shBrushXml.js +71 -0
  110. data/website/scripts/shCore.js +30 -0
  111. data/website/scripts/shLegacy.js +30 -0
  112. data/website/styles/friendly.css +103 -0
  113. data/website/styles/help.png +0 -0
  114. data/website/styles/ie.css +35 -0
  115. data/website/styles/magnifier.png +0 -0
  116. data/website/styles/page_white_code.png +0 -0
  117. data/website/styles/page_white_copy.png +0 -0
  118. data/website/styles/print.css +29 -0
  119. data/website/styles/printer.png +0 -0
  120. data/website/styles/screen.css +257 -0
  121. data/website/styles/shCore.css +330 -0
  122. data/website/styles/shThemeDefault.css +173 -0
  123. data/website/styles/shThemeDjango.css +176 -0
  124. data/website/styles/shThemeEclipse.css +190 -0
  125. data/website/styles/shThemeEmacs.css +175 -0
  126. data/website/styles/shThemeFadeToGrey.css +177 -0
  127. data/website/styles/shThemeMidnight.css +175 -0
  128. data/website/styles/shThemeRDark.css +175 -0
  129. metadata +302 -0
@@ -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,48 @@
1
+ module Friendly
2
+ class TableCreator
3
+ attr_reader :db, :attr_klass
4
+
5
+ def initialize(db = Friendly.db, attr_klass = Friendly::Attribute)
6
+ @db = db
7
+ @attr_klass = attr_klass
8
+ end
9
+
10
+ def create(table)
11
+ unless db.table_exists?(table.table_name)
12
+ case table
13
+ when DocumentTable
14
+ create_document_table(table)
15
+ when Index
16
+ create_index_table(table)
17
+ end
18
+ end
19
+ end
20
+
21
+ protected
22
+ def create_document_table(table)
23
+ db.create_table(table.table_name) do
24
+ primary_key :added_id
25
+ column :id, :bytea
26
+ String :attributes, :text => true
27
+ Time :created_at
28
+ Time :updated_at
29
+ end
30
+ end
31
+
32
+ def create_index_table(table)
33
+ attr = attr_klass # close around this please
34
+
35
+ db.create_table(table.table_name) do
36
+ column :id, :bytea
37
+ table.fields.flatten.each do |f|
38
+ klass = table.klass.attributes[f].type
39
+ type = attr.custom_type?(klass) ? attr.sql_type(klass) : klass
40
+ column(f, type)
41
+ end
42
+ primary_key table.fields.flatten + [:id]
43
+ unique :id
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -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,148 @@
1
+ require 'friendly/time'
2
+ require 'friendly/attribute'
3
+
4
+ # This class was extracted from the cassandra gem by Evan Weaver
5
+ # As such, it is distributed under the terms of the apache license.
6
+ # See the APACHE-LICENSE file in the root of this project for more information.
7
+ #
8
+ module Friendly
9
+ # UUID format version 1, as specified in RFC 4122, with jitter in place of the mac address and sequence counter.
10
+ class UUID
11
+
12
+ class InvalidVersion < StandardError; end
13
+ class TypeError < ::TypeError; end
14
+
15
+ GREGORIAN_EPOCH_OFFSET = 0x01B2_1DD2_1381_4000 # Oct 15, 1582
16
+
17
+ VARIANT = 0b1000_0000_0000_0000
18
+
19
+ def initialize(bytes = nil)
20
+ case bytes
21
+ when self.class # UUID
22
+ @bytes = bytes.to_s
23
+ when String
24
+ case bytes.size
25
+ when 16 # Raw byte array
26
+ @bytes = bytes
27
+ when 36 # Human-readable UUID representation; inverse of #to_guid
28
+ elements = bytes.split("-")
29
+ raise TypeError, "Expected #{bytes.inspect} to cast to a #{self.class} (malformed UUID representation)" if elements.size != 5
30
+ @bytes = Array(elements.join).pack('H32')
31
+ else
32
+ raise TypeError, "Expected #{bytes.inspect} to cast to a #{self.class} (invalid bytecount)"
33
+ end
34
+
35
+ when Integer
36
+ raise TypeError, "Expected #{bytes.inspect} to cast to a #{self.class} (integer out of range)" if bytes < 0 or bytes > 2**128
37
+ @bytes = [
38
+ (bytes >> 96) & 0xFFFF_FFFF,
39
+ (bytes >> 64) & 0xFFFF_FFFF,
40
+ (bytes >> 32) & 0xFFFF_FFFF,
41
+ bytes & 0xFFFF_FFFF
42
+ ].pack("NNNN")
43
+
44
+ when NilClass, Time
45
+ time = (bytes || Time).stamp * 10 + GREGORIAN_EPOCH_OFFSET
46
+ # See http://github.com/spectra/ruby-uuid/
47
+ @bytes = [
48
+ time & 0xFFFF_FFFF,
49
+ time >> 32,
50
+ ((time >> 48) & 0x0FFF) | 0x1000,
51
+ # Top 3 bytes reserved
52
+ rand(2**13) | VARIANT,
53
+ rand(2**16),
54
+ rand(2**32)
55
+ ].pack("NnnnnN")
56
+
57
+ else
58
+ raise TypeError, "Expected #{bytes.inspect} to cast to a #{self.class} (unknown source class)"
59
+ end
60
+ end
61
+
62
+ def to_i
63
+ ints = @bytes.unpack("NNNN")
64
+ (ints[0] << 96) +
65
+ (ints[1] << 64) +
66
+ (ints[2] << 32) +
67
+ ints[3]
68
+ end
69
+
70
+ def version
71
+ time_high = @bytes.unpack("NnnQ")[2]
72
+ version = (time_high & 0xF000).to_s(16)[0].chr.to_i
73
+ version > 0 and version < 6 ? version : -1
74
+ end
75
+
76
+ def variant
77
+ @bytes.unpack('QnnN')[1] >> 13
78
+ end
79
+
80
+ def to_guid
81
+ elements = @bytes.unpack("NnnCCa6")
82
+ node = elements[-1].unpack('C*')
83
+ elements[-1] = '%02x%02x%02x%02x%02x%02x' % node
84
+ "%08x-%04x-%04x-%02x%02x-%s" % elements
85
+ end
86
+
87
+ def to_json(*args)
88
+ to_guid.to_json(*args)
89
+ end
90
+
91
+ def seconds
92
+ total_usecs / 1_000_000
93
+ end
94
+
95
+ def usecs
96
+ total_usecs % 1_000_000
97
+ end
98
+
99
+ def <=>(other)
100
+ total_usecs <=> other.send(:total_usecs)
101
+ end
102
+
103
+ def inspect(long = false)
104
+ "<Friendly::UUID##{object_id} time: #{
105
+ Time.at(seconds).inspect
106
+ }, usecs: #{
107
+ usecs
108
+ } jitter: #{
109
+ @bytes.unpack('QQ')[1]
110
+ }" + (long ? ", version: #{version}, variant: #{variant}, guid: #{to_guid}>" : ">")
111
+ end
112
+
113
+ def <=>(other)
114
+ self.to_i <=> other.to_i
115
+ end
116
+
117
+ def hash
118
+ @bytes.hash
119
+ end
120
+
121
+ def eql?(other)
122
+ other.is_a?(Comparable) and @bytes == other.to_s
123
+ end
124
+
125
+ def ==(other)
126
+ self.to_s == other.to_s
127
+ end
128
+
129
+ def to_s
130
+ @bytes
131
+ end
132
+
133
+ def sql_literal(dataset)
134
+ dataset.literal(to_s.to_sequel_blob)
135
+ end
136
+
137
+ private
138
+
139
+ def total_usecs
140
+ elements = @bytes.unpack("NnnQ")
141
+ (elements[0] + (elements[1] << 32) + ((elements[2] & 0x0FFF) << 48) - GREGORIAN_EPOCH_OFFSET) / 10
142
+ end
143
+ end
144
+ end
145
+
146
+ Friendly::Attribute.register_type(Friendly::UUID, 'bytea') do |s|
147
+ Friendly::UUID.new(s)
148
+ end
@@ -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,7 @@
1
+ test:
2
+ :adapter: "mysql"
3
+ :host: "localhost"
4
+ :user: "root"
5
+ :password: "swordfish"
6
+ :database: "friendly_test"
7
+
@@ -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
+
@@ -0,0 +1,12 @@
1
+ class DatabaseFake
2
+ attr_accessor :from
3
+
4
+ def initialize(from)
5
+ @from = from
6
+ end
7
+
8
+ def from(table)
9
+ @from[table]
10
+ end
11
+ end
12
+