wayne-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 (136) 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/examples/friendly.yml +7 -0
  12. data/friendly.gemspec +240 -0
  13. data/lib/friendly.rb +58 -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 +98 -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/data_store.rb +73 -0
  22. data/lib/friendly/document.rb +70 -0
  23. data/lib/friendly/document/associations.rb +50 -0
  24. data/lib/friendly/document/attributes.rb +114 -0
  25. data/lib/friendly/document/convenience.rb +41 -0
  26. data/lib/friendly/document/mixin.rb +15 -0
  27. data/lib/friendly/document/scoping.rb +66 -0
  28. data/lib/friendly/document/storage.rb +63 -0
  29. data/lib/friendly/document_table.rb +56 -0
  30. data/lib/friendly/index.rb +73 -0
  31. data/lib/friendly/indexer.rb +50 -0
  32. data/lib/friendly/memcached.rb +48 -0
  33. data/lib/friendly/newrelic.rb +6 -0
  34. data/lib/friendly/query.rb +42 -0
  35. data/lib/friendly/scope.rb +100 -0
  36. data/lib/friendly/scope_proxy.rb +43 -0
  37. data/lib/friendly/sequel_monkey_patches.rb +34 -0
  38. data/lib/friendly/storage.rb +31 -0
  39. data/lib/friendly/storage_factory.rb +24 -0
  40. data/lib/friendly/storage_proxy.rb +111 -0
  41. data/lib/friendly/table.rb +15 -0
  42. data/lib/friendly/table_creator.rb +50 -0
  43. data/lib/friendly/time.rb +14 -0
  44. data/lib/friendly/translator.rb +33 -0
  45. data/lib/friendly/uuid.rb +148 -0
  46. data/lib/tasks/friendly.rake +7 -0
  47. data/rails/init.rb +3 -0
  48. data/spec/config.yml.example +7 -0
  49. data/spec/fakes/data_store_fake.rb +29 -0
  50. data/spec/fakes/database_fake.rb +12 -0
  51. data/spec/fakes/dataset_fake.rb +28 -0
  52. data/spec/fakes/document.rb +18 -0
  53. data/spec/fakes/serializer_fake.rb +12 -0
  54. data/spec/fakes/time_fake.rb +12 -0
  55. data/spec/integration/ad_hoc_scopes_spec.rb +42 -0
  56. data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
  57. data/spec/integration/batch_insertion_spec.rb +29 -0
  58. data/spec/integration/convenience_api_spec.rb +25 -0
  59. data/spec/integration/count_spec.rb +12 -0
  60. data/spec/integration/default_value_spec.rb +30 -0
  61. data/spec/integration/dirty_tracking_spec.rb +43 -0
  62. data/spec/integration/find_via_cache_spec.rb +101 -0
  63. data/spec/integration/finder_spec.rb +71 -0
  64. data/spec/integration/has_many_spec.rb +18 -0
  65. data/spec/integration/index_spec.rb +57 -0
  66. data/spec/integration/named_scope_spec.rb +34 -0
  67. data/spec/integration/offline_indexing_spec.rb +53 -0
  68. data/spec/integration/pagination_spec.rb +63 -0
  69. data/spec/integration/scope_chaining_spec.rb +22 -0
  70. data/spec/integration/table_creator_spec.rb +69 -0
  71. data/spec/integration/write_through_cache_spec.rb +53 -0
  72. data/spec/spec.opts +1 -0
  73. data/spec/spec_helper.rb +105 -0
  74. data/spec/unit/associations/association_spec.rb +57 -0
  75. data/spec/unit/associations/set_spec.rb +43 -0
  76. data/spec/unit/attribute_spec.rb +125 -0
  77. data/spec/unit/cache_by_id_spec.rb +102 -0
  78. data/spec/unit/cache_spec.rb +21 -0
  79. data/spec/unit/data_store_spec.rb +201 -0
  80. data/spec/unit/document/attributes_spec.rb +130 -0
  81. data/spec/unit/document_spec.rb +318 -0
  82. data/spec/unit/document_table_spec.rb +126 -0
  83. data/spec/unit/friendly_spec.rb +25 -0
  84. data/spec/unit/index_spec.rb +196 -0
  85. data/spec/unit/memcached_spec.rb +114 -0
  86. data/spec/unit/query_spec.rb +104 -0
  87. data/spec/unit/scope_proxy_spec.rb +44 -0
  88. data/spec/unit/scope_spec.rb +113 -0
  89. data/spec/unit/storage_factory_spec.rb +59 -0
  90. data/spec/unit/storage_proxy_spec.rb +244 -0
  91. data/spec/unit/translator_spec.rb +91 -0
  92. data/website/index.html +210 -0
  93. data/website/scripts/clipboard.swf +0 -0
  94. data/website/scripts/shBrushAS3.js +61 -0
  95. data/website/scripts/shBrushBash.js +66 -0
  96. data/website/scripts/shBrushCSharp.js +67 -0
  97. data/website/scripts/shBrushColdFusion.js +102 -0
  98. data/website/scripts/shBrushCpp.js +99 -0
  99. data/website/scripts/shBrushCss.js +93 -0
  100. data/website/scripts/shBrushDelphi.js +57 -0
  101. data/website/scripts/shBrushDiff.js +43 -0
  102. data/website/scripts/shBrushErlang.js +54 -0
  103. data/website/scripts/shBrushGroovy.js +69 -0
  104. data/website/scripts/shBrushJScript.js +52 -0
  105. data/website/scripts/shBrushJava.js +59 -0
  106. data/website/scripts/shBrushJavaFX.js +60 -0
  107. data/website/scripts/shBrushPerl.js +74 -0
  108. data/website/scripts/shBrushPhp.js +91 -0
  109. data/website/scripts/shBrushPlain.js +35 -0
  110. data/website/scripts/shBrushPowerShell.js +76 -0
  111. data/website/scripts/shBrushPython.js +66 -0
  112. data/website/scripts/shBrushRuby.js +57 -0
  113. data/website/scripts/shBrushScala.js +53 -0
  114. data/website/scripts/shBrushSql.js +68 -0
  115. data/website/scripts/shBrushVb.js +58 -0
  116. data/website/scripts/shBrushXml.js +71 -0
  117. data/website/scripts/shCore.js +30 -0
  118. data/website/scripts/shLegacy.js +30 -0
  119. data/website/styles/friendly.css +103 -0
  120. data/website/styles/help.png +0 -0
  121. data/website/styles/ie.css +35 -0
  122. data/website/styles/magnifier.png +0 -0
  123. data/website/styles/page_white_code.png +0 -0
  124. data/website/styles/page_white_copy.png +0 -0
  125. data/website/styles/print.css +29 -0
  126. data/website/styles/printer.png +0 -0
  127. data/website/styles/screen.css +257 -0
  128. data/website/styles/shCore.css +330 -0
  129. data/website/styles/shThemeDefault.css +173 -0
  130. data/website/styles/shThemeDjango.css +176 -0
  131. data/website/styles/shThemeEclipse.css +190 -0
  132. data/website/styles/shThemeEmacs.css +175 -0
  133. data/website/styles/shThemeFadeToGrey.css +177 -0
  134. data/website/styles/shThemeMidnight.css +175 -0
  135. data/website/styles/shThemeRDark.css +175 -0
  136. metadata +337 -0
@@ -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
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,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
@@ -0,0 +1,114 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Creating and retrieving an object" do
4
+ before do
5
+ @user = User.new :name => "Stewie Griffin",
6
+ :age => 3
7
+ @user.save
8
+ @found_user = User.find(@user.id)
9
+ end
10
+
11
+ it "finds the user in the database" do
12
+ @found_user.name.should == @user.name
13
+ @found_user.age.should == @user.age
14
+ @found_user.id.should == @user.id
15
+ end
16
+
17
+ it "locates an object that is == to the created object" do
18
+ @found_user.should == @user
19
+ end
20
+
21
+ it "sets the created_at timestamp for the record" do
22
+ @user.created_at.should_not be_nil
23
+ @user.created_at.should be_instance_of(Time)
24
+ end
25
+
26
+ it "sets the created_at on the way out of the database" do
27
+ @found_user.created_at.should_not be_nil
28
+ @found_user.created_at.to_i.should == @user.created_at.to_i
29
+ end
30
+
31
+ it "sets the updated_at on the way out of the database" do
32
+ @found_user.updated_at.should_not be_nil
33
+ @found_user.updated_at.to_i.should == @user.updated_at.to_i
34
+ end
35
+
36
+ it "sets the updated_at" do
37
+ @user.updated_at.to_i.should == @user.created_at.to_i
38
+ end
39
+
40
+ it "doesn't serialize id, created_at, or updated_at in the attributes column" do
41
+ result = $db.from("users").first(:id => @user.id)
42
+ attrs = JSON.parse(result[:attributes])
43
+ attrs.keys.should_not include("id")
44
+ attrs.keys.should_not include("created_at")
45
+ attrs.keys.should_not include("updated_at")
46
+ end
47
+
48
+ it "has an id of type Friendly::UUID" do
49
+ @user.id.should be_kind_of(Friendly::UUID)
50
+ end
51
+ end
52
+
53
+ describe "Updating an object" do
54
+ before do
55
+ @user = User.new :name => "Stewie Griffin",
56
+ :age => 3
57
+ @user.save
58
+ @created_id = @user.id
59
+ @created_at = @user.created_at
60
+
61
+ sleep(0.1)
62
+ @user.name = "Brian Griffin"
63
+ @user.age = 8
64
+ @user.save
65
+
66
+ @found_user = User.find(@created_id)
67
+ end
68
+
69
+ it "sets the updated_at column" do
70
+ @user.updated_at.should_not be_nil
71
+ @user.updated_at.should_not == @user.created_at
72
+ @user.updated_at.should > @user.created_at
73
+ end
74
+
75
+ it "doesn't change the created_at" do
76
+ @user.created_at.should == @created_at
77
+ end
78
+
79
+ it "doesn't change the id" do
80
+ @user.id.should == @created_id
81
+ end
82
+
83
+ it "saves the new attrs to the db" do
84
+ @found_user.name.should == "Brian Griffin"
85
+ @found_user.age.should == 8
86
+ end
87
+ end
88
+
89
+ describe "destroying a document" do
90
+ before do
91
+ @user = User.new :name => "Stewie Griffin"
92
+ @user.save
93
+ @user.destroy
94
+ end
95
+
96
+ it "removes it from the database" do
97
+ User.first(:id => @user.id).should be_nil
98
+ end
99
+ end
100
+
101
+ describe "Finding an object by id" do
102
+ it "raises Friendly::RecordNotFound if it doesn't exist" do
103
+ lambda { User.find(12345) }.should raise_error(Friendly::RecordNotFound)
104
+ end
105
+ end
106
+
107
+ describe "An object that has a foreign key" do
108
+ it "is saveable in the database" do
109
+ lambda {
110
+ Address.create :user_id => Friendly::UUID.new
111
+ }.should_not raise_error
112
+ end
113
+ end
114
+