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,29 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Batch inserting several documents" do
4
+ it "inserts them when the block returns" do
5
+ Friendly.batch do
6
+ user = User.new(:name => "Lois")
7
+ user.save
8
+ User.all(:name => "Lois").should be_empty
9
+ end
10
+
11
+ User.all(:name => "Lois").should_not be_empty
12
+ end
13
+
14
+ it "doesn't insert anything if an error is raised" do
15
+ begin
16
+ Friendly.batch do
17
+ user = User.new(:name => "Meg")
18
+ user.save
19
+ raise "AHHHH!"
20
+ end
21
+ rescue RuntimeError => e
22
+ @bubbled_up = true
23
+ end
24
+
25
+ @bubbled_up.should be_true
26
+
27
+ User.all(:name => "Meg").should be_empty
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Document.create" do
4
+ before do
5
+ @user = User.create(:name => "James")
6
+ end
7
+
8
+ it "initializes the document, saves it, and returns it" do
9
+ @user.name.should == "James"
10
+ @user.should_not be_new_record
11
+ User.find(@user.id).should_not be_nil
12
+ end
13
+ end
14
+
15
+ describe "Document#update_attributes" do
16
+ before do
17
+ @user = User.create
18
+ @user.update_attributes :name => "James"
19
+ @user = User.find(@user.id)
20
+ end
21
+
22
+ it "updates the attributes in the database" do
23
+ @user.name.should == "James"
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Counting the objects matching a query" do
4
+ before do
5
+ User.all(:name => "Evil Monkey").each { |u| u.destroy }
6
+ 5.times { User.create :name => "Evil Monkey" }
7
+ end
8
+
9
+ it "does a count in the index to find out what matches" do
10
+ User.count(:name => "Evil Monkey").should == 5
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "An attribute with a default value" do
4
+ describe "before saving" do
5
+ before do
6
+ @user = User.new
7
+ end
8
+
9
+ it "has the value by default" do
10
+ @user.happy.should be_true
11
+ end
12
+
13
+ it "has a default vaue even when it's false" do
14
+ @user.sad.should be_false
15
+ end
16
+ end
17
+
18
+ describe "after saving" do
19
+ before do
20
+ @user = User.new
21
+ @user.save
22
+ @user = User.find(@user.id)
23
+ end
24
+
25
+ it "doesn't set the existing attributes as dirty" do
26
+ @user.should_not be_changed
27
+ @user.should_not be_happy_changed
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Changing an attribute" do
4
+ describe "before a record is saved" do
5
+ before do
6
+ @user = User.new
7
+ @user.name = "James"
8
+ end
9
+
10
+ it "responds to the attribute being changed" do
11
+ @user.should be_name_changed
12
+ end
13
+
14
+ it "returns the original value of the attribute" do
15
+ @user.name_was.should == ""
16
+ end
17
+
18
+ it "is changed" do
19
+ @user.should be_changed
20
+ end
21
+ end
22
+
23
+ describe "after saving a record with changed attributes" do
24
+ before do
25
+ @user = User.create
26
+ @user.name = "James"
27
+ @user.save
28
+ end
29
+
30
+ it "is no longer attribute_changed?" do
31
+ @user.should_not be_name_changed
32
+ end
33
+
34
+ it "returns nil for attribute_was" do
35
+ @user.name_was.should be_nil
36
+ end
37
+
38
+ it "is no longer changed" do
39
+ @user.should_not be_changed
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,101 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Finding objects in the cache" do
4
+ def cache_key(user)
5
+ ["Address", 0, user.id.to_guid].join("/")
6
+ end
7
+
8
+ describe "by id" do
9
+ before do
10
+ @address = Address.create :street => "Spooner"
11
+ end
12
+
13
+ describe "if the user is in cache" do
14
+ before do
15
+ $cache.set(cache_key(@address), "the address")
16
+ end
17
+
18
+ it "returns the object from cache if its there" do
19
+ Address.first(:id => @address.id).should == "the address"
20
+ end
21
+ end
22
+
23
+ describe "if the user isn't there" do
24
+ before do
25
+ $cache.delete(cache_key(@address))
26
+ @found = Address.first(:id => @address.id)
27
+ end
28
+
29
+ it "finds the object in the database" do
30
+ @found.should == @address
31
+ end
32
+
33
+ it "stores the object in cache (read through)" do
34
+ $cache.get(cache_key(@address)).should == @address
35
+ end
36
+ end
37
+ end
38
+
39
+ describe "several objects via id" do
40
+ before do
41
+ @addresses = (0..3).map { Address.create :street => "Spooner" }
42
+ end
43
+
44
+ describe "when all objects are found" do
45
+ before do
46
+ @addresses.each { |a| $cache.set(cache_key(a), "the address") }
47
+ end
48
+
49
+ it "returns all the objects from cache" do
50
+ Address.all(:id => @addresses.map { |u| u.id }).should == ["the address"] * 4
51
+ end
52
+ end
53
+
54
+ describe "when some objects are missing" do
55
+ before do
56
+ $cache.delete(cache_key(@addresses.first))
57
+ @found = Address.all(:id => @addresses.map { |u| u.id })
58
+ end
59
+
60
+ it "finds the object and returns it" do
61
+ expected = @addresses.sort { |a,b| a.id <=> b.id }
62
+ @found.sort { |a,b| a.id <=> b.id }.should == expected
63
+ end
64
+
65
+ it "writes the object through to the cache" do
66
+ lambda {
67
+ $cache.get(cache_key(@addresses.first))
68
+ }.should_not raise_error
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "calling all when the array of ids is empty" do
74
+ it "returns nothing" do
75
+ Address.all(:id => [])
76
+ end
77
+ end
78
+
79
+ describe "finding several objects in order via cache" do
80
+ before do
81
+ @addresses = (0...5).map { Address.create :street => "Spooner" }
82
+ end
83
+
84
+ it "returns them in order" do
85
+ fnd = Address.all(:id => @addresses.map { |a| a.id }, :preserve_order! => true)
86
+ fnd.should == @addresses
87
+ end
88
+ end
89
+
90
+ describe "when there's an object in the index that doesn't really exist" do
91
+ before do
92
+ table = $db.from("index_addresses_on_street")
93
+ table.where(:street => "Quahog").delete
94
+ table.insert(:street => "Quahog", :id => "12345")
95
+ end
96
+
97
+ it "just returns [] silently on #all" do
98
+ Address.all(:street => "Quahog").should == []
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,71 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+ require 'active_support/duration'
3
+ require 'active_support/core_ext/integer'
4
+ require 'active_support/core_ext/time'
5
+ require 'active_support/core_ext/object'
6
+ require 'active_support/core_ext/numeric'
7
+
8
+ describe "Finding multiple objects by id" do
9
+ before do
10
+ @user_one = User.new
11
+ @user_two = User.new
12
+ @user_one.save
13
+ @user_two.save
14
+ @users = User.all(:id => [@user_one.id, @user_two.id])
15
+ end
16
+
17
+ it "finds the objects in the database" do
18
+ @users.length.should == 2
19
+ @users.should include(@user_one)
20
+ @users.should include(@user_two)
21
+ end
22
+
23
+ describe "when no objects are found" do
24
+ it "returns an empty array" do
25
+ User.all(:id => [9999, 12345, 999]).should == []
26
+ end
27
+ end
28
+
29
+ describe "when one object is found, but others aren't" do
30
+ it "returns the found objects" do
31
+ User.all(:id => [@user_one.id, 12345]).should == [@user_one]
32
+ end
33
+ end
34
+ end
35
+
36
+ describe "Limiting a query" do
37
+ before do
38
+ 10.times { User.new(:name => "Stewie").save }
39
+ @results = User.all(:name => "Stewie", :limit! => 5)
40
+ end
41
+
42
+ it "returns the number of results you asked for" do
43
+ @results.length.should == 5
44
+ end
45
+ end
46
+
47
+ describe "limiting a query with offset" do
48
+ before do
49
+ @objects = (0..10).map do |i|
50
+ User.new(:name => "Joe", :created_at => i.minutes.from_now).tap do |u|
51
+ u.save
52
+ end
53
+ end
54
+ end
55
+
56
+ after { @objects.each { |o| o.destroy } }
57
+
58
+ it "returns results starting from the offset to hte limit" do
59
+ User.all(:name => "Joe",
60
+ :offset! => 2,
61
+ :limit! => 2,
62
+ :order! => :created_at.desc).should == @objects.reverse.slice(2, 2)
63
+ end
64
+ end
65
+
66
+ describe "all with only order" do
67
+ it "queries the index" do
68
+ Address.create
69
+ Address.all(:order! => :created_at.desc, :limit! => 5)
70
+ end
71
+ end
@@ -0,0 +1,18 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Has many associations" do
4
+ before do
5
+ @user = User.create :name => "Fred"
6
+ @addresses = (0..2).map { Address.create :user_id => @user.id }
7
+ end
8
+
9
+ it "returns the objects whose foreign keys match the object's id" do
10
+ found = @user.addresses.all.sort { |a, b| a.id <=> b.id }
11
+ found.should == @addresses.sort { |a, b| a.id <=> b.id }
12
+ end
13
+
14
+ it "accepts class_name and foreign_key overrides" do
15
+ found = @user.addresses_override.all.sort { |a, b| a.id <=> b.id }
16
+ found.should == @addresses.sort { |a, b| a.id <=> b.id }
17
+ end
18
+ end
@@ -0,0 +1,57 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+ require 'active_support/duration'
3
+ require 'active_support/core_ext/integer'
4
+ require 'active_support/core_ext/time'
5
+ require 'active_support/core_ext/object'
6
+ require 'active_support/core_ext/numeric'
7
+
8
+ describe "Finding one object with an index" do
9
+ before do
10
+ @created_user = User.new(:name => "Cleveland")
11
+ @created_user.save
12
+ @found_user = User.first(:name => "Cleveland")
13
+ end
14
+
15
+ it "finds the object" do
16
+ @found_user.should == @created_user
17
+ end
18
+ end
19
+
20
+ describe "Finding one object that doesn't exist in the index" do
21
+ it "returns nil" do
22
+ User.first(:name => "asdf").should be_nil
23
+ end
24
+ end
25
+
26
+ describe "Finding object on an index that doesn't exist" do
27
+ it "raises Friendly::MissingIndex" do
28
+ lambda {
29
+ User.first(:age => 3)
30
+ }.should raise_error(Friendly::MissingIndex)
31
+ end
32
+ end
33
+
34
+ describe "Finding ordered objects" do
35
+ before do
36
+ @three = User.new(:name => "Brian", :created_at => 4.hours.ago)
37
+ @two = User.new(:name => "Brian", :created_at => 2.hours.ago)
38
+ @one = User.new(:name => "Brian", :created_at => 5.minutes.ago)
39
+ [@one, @two, @three].each { |i| i.save }
40
+ end
41
+
42
+ after do
43
+ [@one, @two, @three].each { |i| i.destroy }
44
+ end
45
+
46
+ it "can return the objects in desc order" do
47
+ found_users = User.all(:name => "Brian", :order! => :created_at.desc)
48
+ found_users.should == [@one, @two, @three]
49
+ end
50
+
51
+ it "can return the objects in asc order" do
52
+ found_users = User.all(:name => "Brian", :order! => :created_at.asc)
53
+ found_users.length.should == 3
54
+ found_users.should == [@three, @two, @one]
55
+ end
56
+ end
57
+
@@ -0,0 +1,34 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "named_scope" do
4
+ describe "calling a single named_scope" do
5
+ before do
6
+ User.all(:name => "Quagmire").each { |q| q.destroy }
7
+ 5.times { User.create(:name => "Quagmire") }
8
+ end
9
+
10
+ describe "all" do
11
+ it "returns all objects matching the conditions" do
12
+ User.named_quagmire.all.should == User.all(:name => "Quagmire")
13
+ end
14
+
15
+ it "accepts extra conditions" do
16
+ User.create(:name => "Fred")
17
+ found = User.named_quagmire.all(:name => "Fred")
18
+ found.should == User.all(:name => "Fred")
19
+ end
20
+ end
21
+
22
+ describe "first" do
23
+ it "returns the first object matching the conditions" do
24
+ User.named_quagmire.first.should == User.first(:name => "Quagmire")
25
+ end
26
+
27
+ it "accepts extra conditions" do
28
+ User.create(:name => "Fred")
29
+ found = User.named_quagmire.first(:name => "Fred")
30
+ found.should == User.first(:name => "Fred")
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,53 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Building an index offline" do
4
+ before do
5
+ $db.drop_table :awesome_things if $db.table_exists?(:awesome_things)
6
+
7
+ if $db.table_exists?(:index_awesome_things_on_name)
8
+ $db.drop_table :index_awesome_things_on_name
9
+ end
10
+
11
+ @klass = Class.new do
12
+ def self.name; "AwesomeThing"; end
13
+ def self.table_name; "awesome_things"; end
14
+
15
+ include Friendly::Document
16
+
17
+ attribute :name, String
18
+ end
19
+ @klass.create_tables!
20
+
21
+ @jameses = [@klass.create(:name => "James"), @klass.create(:name => "James")]
22
+
23
+ @klass.indexes :name
24
+ @klass.create_tables!
25
+ end
26
+
27
+ describe "" do
28
+ before do
29
+ Friendly::Indexer.populate(@klass, :name)
30
+ end
31
+
32
+ it "builds the missing index rows for all the rows in the doc table" do
33
+ @klass.all(:name => "James").should == @jameses
34
+ end
35
+
36
+ it "ignores records that are already in the index" do
37
+ lambda {
38
+ Friendly::Indexer.populate(@klass, :name)
39
+ }.should_not raise_error
40
+ end
41
+ end
42
+
43
+ describe "with more than `Indexer.objects_per_iteration` objects" do
44
+ before do
45
+ Friendly::Indexer.objects_per_iteration = 1
46
+ Friendly::Indexer.populate(@klass, :name)
47
+ end
48
+
49
+ it "builds the missing index rows for all the rows in the doc table" do
50
+ @klass.all(:name => "James").should == @jameses
51
+ end
52
+ end
53
+ end