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,56 @@
1
+ require 'friendly/table'
2
+
3
+ module Friendly
4
+ class DocumentTable < Table
5
+ attr_reader :klass, :translator
6
+
7
+ def initialize(klass, datastore=Friendly.datastore, translator=Translator.new)
8
+ super(datastore)
9
+ @klass = klass
10
+ @translator = translator
11
+ end
12
+
13
+ def table_name
14
+ klass.table_name
15
+ end
16
+
17
+ def satisfies?(query)
18
+ query.conditions.keys == [:id] && !query.order
19
+ end
20
+
21
+ def create(document)
22
+ record = translator.to_record(document)
23
+ datastore.insert(document, record)
24
+ update_document(document, record)
25
+ end
26
+
27
+ def update(document)
28
+ record = translator.to_record(document)
29
+ datastore.update(document, document.id, record)
30
+ update_document(document, record)
31
+ end
32
+
33
+ def destroy(document)
34
+ datastore.delete(document, document.id)
35
+ end
36
+
37
+ def first(query)
38
+ record = datastore.first(klass, query)
39
+ record && to_object(record)
40
+ end
41
+
42
+ def all(query)
43
+ datastore.all(klass, query).map { |r| to_object(r) }
44
+ end
45
+
46
+ protected
47
+ def update_document(document, record)
48
+ attrs = record.reject { |k,v| k == :attributes }.merge(:new_record => false)
49
+ document.attributes = attrs
50
+ end
51
+
52
+ def to_object(record)
53
+ translator.to_object(klass, record)
54
+ end
55
+ end
56
+ end
@@ -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,17 @@
1
+ require 'friendly/scope'
2
+
3
+ module Friendly
4
+ class NamedScope
5
+ attr_reader :klass, :parameters, :scope_klass
6
+
7
+ def initialize(klass, parameters, scope_klass = Scope)
8
+ @klass = klass
9
+ @parameters = parameters
10
+ @scope_klass = scope_klass
11
+ end
12
+
13
+ def scope
14
+ @scope_klass.new(@klass, @parameters)
15
+ end
16
+ end
17
+ 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,100 @@
1
+ module Friendly
2
+ class Scope
3
+ attr_reader :klass, :parameters
4
+
5
+ def initialize(klass, parameters)
6
+ @klass = klass
7
+ @parameters = parameters
8
+ end
9
+
10
+ # Fetch all documents at this scope.
11
+ #
12
+ # @param [Hash] extra_parameters add extra parameters to this query.
13
+ #
14
+ def all(extra_parameters = {})
15
+ klass.all(params(extra_parameters))
16
+ end
17
+
18
+ # Fetch the first document at this scope.
19
+ #
20
+ # @param [Hash] extra_parameters add extra parameters to this query.
21
+ #
22
+ def first(extra_parameters = {})
23
+ klass.first(params(extra_parameters))
24
+ end
25
+
26
+ # Paginate the documents at this scope.
27
+ #
28
+ # @param [Hash] extra_parameters add extra parameters to this query.
29
+ # @return WillPaginate::Collection
30
+ #
31
+ def paginate(extra_parameters = {})
32
+ klass.paginate(params(extra_parameters))
33
+ end
34
+
35
+ # Build an object at this scope.
36
+ #
37
+ # e.g.
38
+ # Post.scope(:name => "James").build.name # => "James"
39
+ #
40
+ # @param [Hash] extra_parameters add extra parameters to this query.
41
+ #
42
+ def build(extra_parameters = {})
43
+ klass.new(params_without_modifiers(extra_parameters))
44
+ end
45
+
46
+ # Create an object at this scope.
47
+ #
48
+ # e.g.
49
+ # @post = Post.scope(:name => "James").create
50
+ # @post.new_record? # => false
51
+ # @post.name # => "James"
52
+ #
53
+ # @param [Hash] extra_parameters add extra parameters to this query.
54
+ #
55
+ def create(extra_parameters = {})
56
+ klass.create(params_without_modifiers(extra_parameters))
57
+ end
58
+
59
+ # Override #respond_to? so that we can return true when it's another named_scope.
60
+ #
61
+ # @override
62
+ #
63
+ def respond_to?(method_name, include_private = false)
64
+ klass.has_named_scope?(method_name) || super
65
+ end
66
+
67
+ # Use method_missing to respond to other named scopes on klass.
68
+ #
69
+ # @override
70
+ #
71
+ def method_missing(method_name, *args, &block)
72
+ respond_to?(method_name) ? chain_with(method_name) : super
73
+ end
74
+
75
+ # Chain with another one of klass's named_scopes.
76
+ #
77
+ # @param [Symbol] scope_name The name of the scope to chain with.
78
+ #
79
+ def chain_with(scope_name)
80
+ self + klass.send(scope_name)
81
+ end
82
+
83
+ # Create a new Scope that is the combination of self and other, where other takes priority
84
+ #
85
+ # @param [Friendly::Scope] other The scope to merge with.
86
+ #
87
+ def +(other_scope)
88
+ self.class.new(klass, parameters.merge(other_scope.parameters))
89
+ end
90
+
91
+ protected
92
+ def params(extra)
93
+ parameters.merge(extra)
94
+ end
95
+
96
+ def params_without_modifiers(extra)
97
+ params(extra).reject { |k,v| k.to_s =~ /!$/ }
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,45 @@
1
+ require 'friendly/named_scope'
2
+
3
+ module Friendly
4
+ class ScopeProxy
5
+ attr_reader :klass, :scope_klass, :scopes
6
+
7
+ def initialize(klass, scope_klass = Scope)
8
+ @klass = klass
9
+ @scope_klass = scope_klass
10
+ @scopes = {}
11
+ end
12
+
13
+ def add_named(name, parameters)
14
+ scopes[name] = parameters
15
+ add_scope_method_to_klass(name)
16
+ end
17
+
18
+ def get(name)
19
+ scopes[name]
20
+ end
21
+
22
+ def get_instance(name)
23
+ scope_klass.new(klass, get(name))
24
+ end
25
+
26
+ def ad_hoc(parameters)
27
+ scope_klass.new(klass, parameters)
28
+ end
29
+
30
+ def has_named_scope?(name)
31
+ scopes.has_key?(name)
32
+ end
33
+
34
+ protected
35
+ def add_scope_method_to_klass(scope_name)
36
+ klass.class_eval do
37
+ eval <<-__END__
38
+ def self.#{scope_name}
39
+ scope_proxy.get_instance(:#{scope_name})
40
+ end
41
+ __END__
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,34 @@
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
@@ -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