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.
- data/.document +2 -0
- data/.gitignore +26 -0
- data/APACHE-LICENSE +202 -0
- data/CHANGELOG.md +28 -0
- data/CONTRIBUTORS.md +7 -0
- data/LICENSE +20 -0
- data/README.md +288 -0
- data/Rakefile +68 -0
- data/TODO.md +5 -0
- data/VERSION +1 -0
- data/examples/friendly.yml +7 -0
- data/friendly.gemspec +240 -0
- data/lib/friendly.rb +58 -0
- data/lib/friendly/associations.rb +7 -0
- data/lib/friendly/associations/association.rb +34 -0
- data/lib/friendly/associations/set.rb +37 -0
- data/lib/friendly/attribute.rb +98 -0
- data/lib/friendly/boolean.rb +10 -0
- data/lib/friendly/cache.rb +24 -0
- data/lib/friendly/cache/by_id.rb +33 -0
- data/lib/friendly/data_store.rb +73 -0
- data/lib/friendly/document.rb +70 -0
- data/lib/friendly/document/associations.rb +50 -0
- data/lib/friendly/document/attributes.rb +114 -0
- data/lib/friendly/document/convenience.rb +41 -0
- data/lib/friendly/document/mixin.rb +15 -0
- data/lib/friendly/document/scoping.rb +66 -0
- data/lib/friendly/document/storage.rb +63 -0
- data/lib/friendly/document_table.rb +56 -0
- data/lib/friendly/index.rb +73 -0
- data/lib/friendly/indexer.rb +50 -0
- data/lib/friendly/memcached.rb +48 -0
- data/lib/friendly/newrelic.rb +6 -0
- data/lib/friendly/query.rb +42 -0
- data/lib/friendly/scope.rb +100 -0
- data/lib/friendly/scope_proxy.rb +43 -0
- data/lib/friendly/sequel_monkey_patches.rb +34 -0
- data/lib/friendly/storage.rb +31 -0
- data/lib/friendly/storage_factory.rb +24 -0
- data/lib/friendly/storage_proxy.rb +111 -0
- data/lib/friendly/table.rb +15 -0
- data/lib/friendly/table_creator.rb +50 -0
- data/lib/friendly/time.rb +14 -0
- data/lib/friendly/translator.rb +33 -0
- data/lib/friendly/uuid.rb +148 -0
- data/lib/tasks/friendly.rake +7 -0
- data/rails/init.rb +3 -0
- data/spec/config.yml.example +7 -0
- data/spec/fakes/data_store_fake.rb +29 -0
- data/spec/fakes/database_fake.rb +12 -0
- data/spec/fakes/dataset_fake.rb +28 -0
- data/spec/fakes/document.rb +18 -0
- data/spec/fakes/serializer_fake.rb +12 -0
- data/spec/fakes/time_fake.rb +12 -0
- data/spec/integration/ad_hoc_scopes_spec.rb +42 -0
- data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
- data/spec/integration/batch_insertion_spec.rb +29 -0
- data/spec/integration/convenience_api_spec.rb +25 -0
- data/spec/integration/count_spec.rb +12 -0
- data/spec/integration/default_value_spec.rb +30 -0
- data/spec/integration/dirty_tracking_spec.rb +43 -0
- data/spec/integration/find_via_cache_spec.rb +101 -0
- data/spec/integration/finder_spec.rb +71 -0
- data/spec/integration/has_many_spec.rb +18 -0
- data/spec/integration/index_spec.rb +57 -0
- data/spec/integration/named_scope_spec.rb +34 -0
- data/spec/integration/offline_indexing_spec.rb +53 -0
- data/spec/integration/pagination_spec.rb +63 -0
- data/spec/integration/scope_chaining_spec.rb +22 -0
- data/spec/integration/table_creator_spec.rb +69 -0
- data/spec/integration/write_through_cache_spec.rb +53 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +105 -0
- data/spec/unit/associations/association_spec.rb +57 -0
- data/spec/unit/associations/set_spec.rb +43 -0
- data/spec/unit/attribute_spec.rb +125 -0
- data/spec/unit/cache_by_id_spec.rb +102 -0
- data/spec/unit/cache_spec.rb +21 -0
- data/spec/unit/data_store_spec.rb +201 -0
- data/spec/unit/document/attributes_spec.rb +130 -0
- data/spec/unit/document_spec.rb +318 -0
- data/spec/unit/document_table_spec.rb +126 -0
- data/spec/unit/friendly_spec.rb +25 -0
- data/spec/unit/index_spec.rb +196 -0
- data/spec/unit/memcached_spec.rb +114 -0
- data/spec/unit/query_spec.rb +104 -0
- data/spec/unit/scope_proxy_spec.rb +44 -0
- data/spec/unit/scope_spec.rb +113 -0
- data/spec/unit/storage_factory_spec.rb +59 -0
- data/spec/unit/storage_proxy_spec.rb +244 -0
- data/spec/unit/translator_spec.rb +91 -0
- data/website/index.html +210 -0
- data/website/scripts/clipboard.swf +0 -0
- data/website/scripts/shBrushAS3.js +61 -0
- data/website/scripts/shBrushBash.js +66 -0
- data/website/scripts/shBrushCSharp.js +67 -0
- data/website/scripts/shBrushColdFusion.js +102 -0
- data/website/scripts/shBrushCpp.js +99 -0
- data/website/scripts/shBrushCss.js +93 -0
- data/website/scripts/shBrushDelphi.js +57 -0
- data/website/scripts/shBrushDiff.js +43 -0
- data/website/scripts/shBrushErlang.js +54 -0
- data/website/scripts/shBrushGroovy.js +69 -0
- data/website/scripts/shBrushJScript.js +52 -0
- data/website/scripts/shBrushJava.js +59 -0
- data/website/scripts/shBrushJavaFX.js +60 -0
- data/website/scripts/shBrushPerl.js +74 -0
- data/website/scripts/shBrushPhp.js +91 -0
- data/website/scripts/shBrushPlain.js +35 -0
- data/website/scripts/shBrushPowerShell.js +76 -0
- data/website/scripts/shBrushPython.js +66 -0
- data/website/scripts/shBrushRuby.js +57 -0
- data/website/scripts/shBrushScala.js +53 -0
- data/website/scripts/shBrushSql.js +68 -0
- data/website/scripts/shBrushVb.js +58 -0
- data/website/scripts/shBrushXml.js +71 -0
- data/website/scripts/shCore.js +30 -0
- data/website/scripts/shLegacy.js +30 -0
- data/website/styles/friendly.css +103 -0
- data/website/styles/help.png +0 -0
- data/website/styles/ie.css +35 -0
- data/website/styles/magnifier.png +0 -0
- data/website/styles/page_white_code.png +0 -0
- data/website/styles/page_white_copy.png +0 -0
- data/website/styles/print.css +29 -0
- data/website/styles/printer.png +0 -0
- data/website/styles/screen.css +257 -0
- data/website/styles/shCore.css +330 -0
- data/website/styles/shThemeDefault.css +173 -0
- data/website/styles/shThemeDjango.css +176 -0
- data/website/styles/shThemeEclipse.css +190 -0
- data/website/styles/shThemeEmacs.css +175 -0
- data/website/styles/shThemeFadeToGrey.css +177 -0
- data/website/styles/shThemeMidnight.css +175 -0
- data/website/styles/shThemeRDark.css +175 -0
- metadata +337 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'friendly/document/mixin'
|
|
2
|
+
|
|
3
|
+
module Friendly
|
|
4
|
+
module Document
|
|
5
|
+
module Convenience
|
|
6
|
+
extend Mixin
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
attr_writer :collection_klass
|
|
10
|
+
|
|
11
|
+
def collection_klass
|
|
12
|
+
@collection_klass ||= WillPaginate::Collection
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def find(id)
|
|
16
|
+
doc = first(:id => id)
|
|
17
|
+
raise RecordNotFound, "Couldn't find #{name}/#{id}" if doc.nil?
|
|
18
|
+
doc
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def paginate(conditions)
|
|
22
|
+
query = query(conditions)
|
|
23
|
+
count = count(query)
|
|
24
|
+
collection = collection_klass.new(query.page, query.per_page, count)
|
|
25
|
+
collection.replace(all(query))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create(attributes = {})
|
|
29
|
+
doc = new(attributes)
|
|
30
|
+
doc.save
|
|
31
|
+
doc
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def update_attributes(attributes)
|
|
36
|
+
self.attributes = attributes
|
|
37
|
+
save
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Friendly
|
|
2
|
+
module Document
|
|
3
|
+
module Mixin
|
|
4
|
+
# FIXME: I'm not in love with this. But, I also don't think it's the
|
|
5
|
+
# end of the world.
|
|
6
|
+
def included(klass)
|
|
7
|
+
if klass.const_defined?(:ClassMethods)
|
|
8
|
+
klass.const_get(:ClassMethods).send(:include, const_get(:ClassMethods))
|
|
9
|
+
else
|
|
10
|
+
klass.send(:extend, const_get(:ClassMethods))
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'friendly/document/mixin'
|
|
2
|
+
|
|
3
|
+
module Friendly
|
|
4
|
+
module Document
|
|
5
|
+
module Scoping
|
|
6
|
+
extend Mixin
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
attr_writer :scope_proxy
|
|
10
|
+
|
|
11
|
+
def scope_proxy
|
|
12
|
+
@scope_proxy ||= ScopeProxy.new(self)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Add a named scope to this Document.
|
|
16
|
+
#
|
|
17
|
+
# e.g.
|
|
18
|
+
#
|
|
19
|
+
# class Post
|
|
20
|
+
# indexes :created_at
|
|
21
|
+
# named_scope :recent, :order! => :created_at.desc
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# Then, you can access the recent posts with:
|
|
25
|
+
#
|
|
26
|
+
# Post.recent.all
|
|
27
|
+
# ...or...
|
|
28
|
+
# Post.recent.first
|
|
29
|
+
#
|
|
30
|
+
# Both #all and #first also take additional parameters:
|
|
31
|
+
#
|
|
32
|
+
# Post.recent.all(:author_id => @author.id)
|
|
33
|
+
#
|
|
34
|
+
# Scopes are also chainable. See the README or Friendly::Scope docs for details.
|
|
35
|
+
#
|
|
36
|
+
# @param [Symbol] name the name of the scope.
|
|
37
|
+
# @param [Hash] parameters the query that this named scope will perform.
|
|
38
|
+
#
|
|
39
|
+
def named_scope(name, parameters)
|
|
40
|
+
scope_proxy.add_named(name, parameters)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns boolean based on whether the Document has a scope by a particular name.
|
|
44
|
+
#
|
|
45
|
+
# @param [Symbol] name The name of the scope in question.
|
|
46
|
+
#
|
|
47
|
+
def has_named_scope?(name)
|
|
48
|
+
scope_proxy.has_named_scope?(name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Create an ad hoc scope on this Document.
|
|
52
|
+
#
|
|
53
|
+
# e.g.
|
|
54
|
+
#
|
|
55
|
+
# scope = Post.scope(:order! => :created_at)
|
|
56
|
+
# scope.all # => [#<Post>, #<Post>]
|
|
57
|
+
#
|
|
58
|
+
# @param [Hash] parameters the query parameters to create the scope with.
|
|
59
|
+
#
|
|
60
|
+
def scope(parameters)
|
|
61
|
+
scope_proxy.ad_hoc(parameters)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'friendly/document/mixin'
|
|
2
|
+
|
|
3
|
+
module Friendly
|
|
4
|
+
module Document
|
|
5
|
+
module Storage
|
|
6
|
+
extend Mixin
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
attr_writer :storage_proxy, :query_klass
|
|
10
|
+
|
|
11
|
+
def create_tables!
|
|
12
|
+
storage_proxy.create_tables!
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def storage_proxy
|
|
16
|
+
@storage_proxy ||= StorageProxy.new(self)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def indexes(*args)
|
|
20
|
+
storage_proxy.add(args)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def caches_by(*fields)
|
|
24
|
+
options = fields.last.is_a?(Hash) ? fields.pop : {}
|
|
25
|
+
storage_proxy.cache(fields, options)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def first(query)
|
|
29
|
+
storage_proxy.first(query(query))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def all(query)
|
|
33
|
+
storage_proxy.all(query(query))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def count(conditions)
|
|
37
|
+
storage_proxy.count(query(conditions))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def query_klass
|
|
41
|
+
@query_klass ||= Query
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
protected
|
|
45
|
+
def query(conditions)
|
|
46
|
+
conditions.is_a?(Query) ? conditions : query_klass.new(conditions)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def save
|
|
51
|
+
new_record? ? storage_proxy.create(self) : storage_proxy.update(self)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def destroy
|
|
55
|
+
storage_proxy.destroy(self)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def storage_proxy
|
|
59
|
+
self.class.storage_proxy
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -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,50 @@
|
|
|
1
|
+
module Friendly
|
|
2
|
+
class Indexer
|
|
3
|
+
class << self
|
|
4
|
+
attr_accessor :objects_per_iteration
|
|
5
|
+
|
|
6
|
+
def populate(klass, *fields)
|
|
7
|
+
instance.populate(klass, klass.storage_proxy.index_for_fields(fields))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def instance
|
|
11
|
+
@instance ||= new
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
self.objects_per_iteration = 100
|
|
16
|
+
|
|
17
|
+
attr_reader :datastore, :translator
|
|
18
|
+
|
|
19
|
+
def initialize(datastore = Friendly.datastore, translator = Translator.new)
|
|
20
|
+
@datastore = datastore
|
|
21
|
+
@translator = translator
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def populate(klass, index)
|
|
25
|
+
count = 0
|
|
26
|
+
loop do
|
|
27
|
+
rows = datastore.all(klass, Query.new(:offset! => count,
|
|
28
|
+
:limit! => objects_per_iteration,
|
|
29
|
+
:order! => :added_id.asc))
|
|
30
|
+
rows.each do |attrs|
|
|
31
|
+
begin
|
|
32
|
+
index.create(translator.to_object(klass, attrs))
|
|
33
|
+
rescue Sequel::DatabaseError
|
|
34
|
+
# we can safely swallow this exception because if we've gotten
|
|
35
|
+
# to this point, we can be pretty sure that it's a duplicate
|
|
36
|
+
# key error, which just means that the object already exists
|
|
37
|
+
# in the index
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
break if rows.length < objects_per_iteration
|
|
41
|
+
count += objects_per_iteration
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
protected
|
|
46
|
+
def objects_per_iteration
|
|
47
|
+
self.class.objects_per_iteration
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
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,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
|