arunthampi-friendly 0.5.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.
Files changed (137) hide show
  1. data/.document +2 -0
  2. data/.gitignore +26 -0
  3. data/APACHE-LICENSE +202 -0
  4. data/CHANGELOG.md +28 -0
  5. data/CONTRIBUTORS.md +7 -0
  6. data/LICENSE +20 -0
  7. data/README.md +288 -0
  8. data/Rakefile +68 -0
  9. data/TODO.md +5 -0
  10. data/VERSION +1 -0
  11. data/arunthampi-friendly.gemspec +241 -0
  12. data/examples/friendly.yml +7 -0
  13. data/friendly.gemspec +240 -0
  14. data/lib/friendly.rb +53 -0
  15. data/lib/friendly/associations.rb +7 -0
  16. data/lib/friendly/associations/association.rb +34 -0
  17. data/lib/friendly/associations/set.rb +37 -0
  18. data/lib/friendly/attribute.rb +98 -0
  19. data/lib/friendly/boolean.rb +10 -0
  20. data/lib/friendly/cache.rb +24 -0
  21. data/lib/friendly/cache/by_id.rb +33 -0
  22. data/lib/friendly/data_store.rb +73 -0
  23. data/lib/friendly/document.rb +70 -0
  24. data/lib/friendly/document/associations.rb +50 -0
  25. data/lib/friendly/document/attributes.rb +114 -0
  26. data/lib/friendly/document/convenience.rb +41 -0
  27. data/lib/friendly/document/mixin.rb +15 -0
  28. data/lib/friendly/document/scoping.rb +66 -0
  29. data/lib/friendly/document/storage.rb +63 -0
  30. data/lib/friendly/document_table.rb +56 -0
  31. data/lib/friendly/index.rb +73 -0
  32. data/lib/friendly/indexer.rb +50 -0
  33. data/lib/friendly/memcached.rb +48 -0
  34. data/lib/friendly/newrelic.rb +6 -0
  35. data/lib/friendly/query.rb +42 -0
  36. data/lib/friendly/scope.rb +100 -0
  37. data/lib/friendly/scope_proxy.rb +43 -0
  38. data/lib/friendly/sequel_monkey_patches.rb +34 -0
  39. data/lib/friendly/storage.rb +31 -0
  40. data/lib/friendly/storage_factory.rb +24 -0
  41. data/lib/friendly/storage_proxy.rb +111 -0
  42. data/lib/friendly/table.rb +15 -0
  43. data/lib/friendly/table_creator.rb +50 -0
  44. data/lib/friendly/time.rb +14 -0
  45. data/lib/friendly/translator.rb +33 -0
  46. data/lib/friendly/uuid.rb +148 -0
  47. data/lib/tasks/friendly.rake +7 -0
  48. data/rails/init.rb +3 -0
  49. data/spec/config.yml.example +7 -0
  50. data/spec/fakes/data_store_fake.rb +29 -0
  51. data/spec/fakes/database_fake.rb +12 -0
  52. data/spec/fakes/dataset_fake.rb +28 -0
  53. data/spec/fakes/document.rb +18 -0
  54. data/spec/fakes/serializer_fake.rb +12 -0
  55. data/spec/fakes/time_fake.rb +12 -0
  56. data/spec/integration/ad_hoc_scopes_spec.rb +42 -0
  57. data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
  58. data/spec/integration/batch_insertion_spec.rb +29 -0
  59. data/spec/integration/convenience_api_spec.rb +25 -0
  60. data/spec/integration/count_spec.rb +12 -0
  61. data/spec/integration/default_value_spec.rb +30 -0
  62. data/spec/integration/dirty_tracking_spec.rb +43 -0
  63. data/spec/integration/find_via_cache_spec.rb +101 -0
  64. data/spec/integration/finder_spec.rb +71 -0
  65. data/spec/integration/has_many_spec.rb +18 -0
  66. data/spec/integration/index_spec.rb +57 -0
  67. data/spec/integration/named_scope_spec.rb +34 -0
  68. data/spec/integration/offline_indexing_spec.rb +53 -0
  69. data/spec/integration/pagination_spec.rb +63 -0
  70. data/spec/integration/scope_chaining_spec.rb +22 -0
  71. data/spec/integration/table_creator_spec.rb +69 -0
  72. data/spec/integration/write_through_cache_spec.rb +53 -0
  73. data/spec/spec.opts +1 -0
  74. data/spec/spec_helper.rb +105 -0
  75. data/spec/unit/associations/association_spec.rb +57 -0
  76. data/spec/unit/associations/set_spec.rb +43 -0
  77. data/spec/unit/attribute_spec.rb +125 -0
  78. data/spec/unit/cache_by_id_spec.rb +102 -0
  79. data/spec/unit/cache_spec.rb +21 -0
  80. data/spec/unit/data_store_spec.rb +201 -0
  81. data/spec/unit/document/attributes_spec.rb +130 -0
  82. data/spec/unit/document_spec.rb +318 -0
  83. data/spec/unit/document_table_spec.rb +126 -0
  84. data/spec/unit/friendly_spec.rb +25 -0
  85. data/spec/unit/index_spec.rb +196 -0
  86. data/spec/unit/memcached_spec.rb +114 -0
  87. data/spec/unit/query_spec.rb +104 -0
  88. data/spec/unit/scope_proxy_spec.rb +44 -0
  89. data/spec/unit/scope_spec.rb +113 -0
  90. data/spec/unit/storage_factory_spec.rb +59 -0
  91. data/spec/unit/storage_proxy_spec.rb +244 -0
  92. data/spec/unit/translator_spec.rb +91 -0
  93. data/website/index.html +210 -0
  94. data/website/scripts/clipboard.swf +0 -0
  95. data/website/scripts/shBrushAS3.js +61 -0
  96. data/website/scripts/shBrushBash.js +66 -0
  97. data/website/scripts/shBrushCSharp.js +67 -0
  98. data/website/scripts/shBrushColdFusion.js +102 -0
  99. data/website/scripts/shBrushCpp.js +99 -0
  100. data/website/scripts/shBrushCss.js +93 -0
  101. data/website/scripts/shBrushDelphi.js +57 -0
  102. data/website/scripts/shBrushDiff.js +43 -0
  103. data/website/scripts/shBrushErlang.js +54 -0
  104. data/website/scripts/shBrushGroovy.js +69 -0
  105. data/website/scripts/shBrushJScript.js +52 -0
  106. data/website/scripts/shBrushJava.js +59 -0
  107. data/website/scripts/shBrushJavaFX.js +60 -0
  108. data/website/scripts/shBrushPerl.js +74 -0
  109. data/website/scripts/shBrushPhp.js +91 -0
  110. data/website/scripts/shBrushPlain.js +35 -0
  111. data/website/scripts/shBrushPowerShell.js +76 -0
  112. data/website/scripts/shBrushPython.js +66 -0
  113. data/website/scripts/shBrushRuby.js +57 -0
  114. data/website/scripts/shBrushScala.js +53 -0
  115. data/website/scripts/shBrushSql.js +68 -0
  116. data/website/scripts/shBrushVb.js +58 -0
  117. data/website/scripts/shBrushXml.js +71 -0
  118. data/website/scripts/shCore.js +30 -0
  119. data/website/scripts/shLegacy.js +30 -0
  120. data/website/styles/friendly.css +103 -0
  121. data/website/styles/help.png +0 -0
  122. data/website/styles/ie.css +35 -0
  123. data/website/styles/magnifier.png +0 -0
  124. data/website/styles/page_white_code.png +0 -0
  125. data/website/styles/page_white_copy.png +0 -0
  126. data/website/styles/print.css +29 -0
  127. data/website/styles/printer.png +0 -0
  128. data/website/styles/screen.css +257 -0
  129. data/website/styles/shCore.css +330 -0
  130. data/website/styles/shThemeDefault.css +173 -0
  131. data/website/styles/shThemeDjango.css +176 -0
  132. data/website/styles/shThemeEclipse.css +190 -0
  133. data/website/styles/shThemeEmacs.css +175 -0
  134. data/website/styles/shThemeFadeToGrey.css +177 -0
  135. data/website/styles/shThemeMidnight.css +175 -0
  136. data/website/styles/shThemeRDark.css +175 -0
  137. metadata +311 -0
@@ -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,50 @@
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
+ binary :id, :size => 16
26
+ String :attributes, :text => true
27
+ Time :created_at
28
+ Time :updated_at
29
+
30
+ unique :id
31
+ end
32
+ end
33
+
34
+ def create_index_table(table)
35
+ attr = attr_klass # close around this please
36
+
37
+ db.create_table(table.table_name) do
38
+ binary :id, :size => 16
39
+ table.fields.flatten.each do |f|
40
+ klass = table.klass.attributes[f].type
41
+ type = attr.custom_type?(klass) ? attr.sql_type(klass) : klass
42
+ column(f, type)
43
+ end
44
+ primary_key table.fields.flatten + [:id]
45
+ unique :id
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -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,33 @@
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
+ attributes.merge!(record).merge!(:new_record => false)
16
+ klass.new_without_change_tracking attributes
17
+ end
18
+
19
+ def to_record(document)
20
+ { :id => document.id,
21
+ :created_at => document.created_at,
22
+ :updated_at => time.new,
23
+ :attributes => serialize(document) }
24
+ end
25
+
26
+ protected
27
+ def serialize(document)
28
+ attrs = document.to_hash.reject { |k,v| RESERVED_ATTRS.include?(k) }
29
+ serializer.generate(attrs)
30
+ end
31
+ end
32
+ end
33
+
@@ -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, 'binary(16)') do |s|
147
+ Friendly::UUID.new(s)
148
+ end
@@ -0,0 +1,7 @@
1
+ namespace :friendly do
2
+ task :build_index do
3
+ klass = ENV['KLASS'].constantize
4
+ fields = ENV['FIELDS'].split(',').map { |f| f.to_sym }
5
+ Friendly::Indexer.populate(klass, *fields)
6
+ end
7
+ 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
+
@@ -0,0 +1,28 @@
1
+ class DatasetFake
2
+ attr_accessor :where, :insert, :inserts, :update, :updates, :first
3
+
4
+ def initialize(opts = {})
5
+ opts.each { |k,v| send("#{k}=", v) }
6
+ @inserts ||= []
7
+ @updates ||= []
8
+ end
9
+
10
+ def where(conditions)
11
+ @where.detect { |k,v| k == conditions }.last
12
+ end
13
+
14
+ def insert(attributes)
15
+ inserts << attributes
16
+ @insert
17
+ end
18
+
19
+ def update(attributes)
20
+ updates << attributes
21
+ @update
22
+ end
23
+
24
+ def first(conditions)
25
+ @first.detect { |k,v| k == conditions }.last
26
+ end
27
+ end
28
+
@@ -0,0 +1,18 @@
1
+ class FakeDocument
2
+ attr_accessor :id, :created_at, :to_hash, :new_record, :table_name,
3
+ :indexes, :name, :updated_at, :where_clause
4
+
5
+ def initialize(opts = {})
6
+ opts.each { |k,v| send("#{k}=", v) }
7
+ new_record = true if new_record.nil?
8
+ end
9
+
10
+ def new_record?
11
+ new_record
12
+ end
13
+
14
+ def attributes=(attrs)
15
+ attrs.each { |k,v| send("#{k}=", v) }
16
+ end
17
+ end
18
+
@@ -0,0 +1,12 @@
1
+ class SerializerFake
2
+ attr_writer :generate
3
+
4
+ def initialize(opts = {})
5
+ opts.each { |k,v| send("#{k}=", v) }
6
+ end
7
+
8
+ def generate(args)
9
+ @generate[args]
10
+ end
11
+ end
12
+
@@ -0,0 +1,12 @@
1
+ class TimeFake
2
+ attr_writer :time
3
+
4
+ def initialize(time)
5
+ @time = time
6
+ end
7
+
8
+ def new
9
+ @time
10
+ end
11
+ end
12
+
@@ -0,0 +1,42 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Querying with an ad-hoc scope" do
4
+ before do
5
+ User.all(:name => "Fred").each { |u| u.destroy }
6
+ @users = (1..10).map { User.create(:name => "Fred") }
7
+ end
8
+
9
+ it "can return all the objects matching the scope" do
10
+ User.scope(:name => "Fred").all.should == @users
11
+ end
12
+
13
+ it "can return the first object matching the scope" do
14
+ User.scope(:name => "Fred").first.should == User.first(:name => "Fred")
15
+ end
16
+
17
+ it "can paginate over the matching objects" do
18
+ found = User.scope(:name => "Fred").paginate(:per_page! => 5)
19
+ found.should == User.paginate(:name => "Fred", :per_page! => 5)
20
+ end
21
+
22
+ it "can build an object at scope" do
23
+ User.scope(:name => "Fred", :limit! => 5).build.name.should == "Fred"
24
+ end
25
+
26
+ it "supports overriding parameters when building" do
27
+ scope = User.scope(:name => "Fred", :limit! => 5)
28
+ scope.build(:name => "Joe").name.should == "Joe"
29
+ end
30
+
31
+ it "can create an object at scope" do
32
+ user = User.scope(:name => "Joe").create
33
+ user.should_not be_new_record
34
+ user.name.should == "Joe"
35
+ end
36
+
37
+ it "supports overriding parameters when creating" do
38
+ user = User.scope(:name => "Joe").create(:name => "Fred")
39
+ user.should_not be_new_record
40
+ user.name.should == "Fred"
41
+ end
42
+ end