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.
- 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/arunthampi-friendly.gemspec +241 -0
- data/examples/friendly.yml +7 -0
- data/friendly.gemspec +240 -0
- data/lib/friendly.rb +53 -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 +311 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'friendly/associations'
|
|
2
|
+
require 'friendly/document/mixin'
|
|
3
|
+
|
|
4
|
+
module Friendly
|
|
5
|
+
module Document
|
|
6
|
+
module Associations
|
|
7
|
+
extend Mixin
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
attr_writer :association_set
|
|
11
|
+
|
|
12
|
+
def association_set
|
|
13
|
+
@association_set ||= Friendly::Associations::Set.new(self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Add a has_many association.
|
|
17
|
+
#
|
|
18
|
+
# e.g.
|
|
19
|
+
#
|
|
20
|
+
# class Post
|
|
21
|
+
# attribute :user_id, Friendly::UUID
|
|
22
|
+
# indexes :user_id
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# class User
|
|
26
|
+
# has_many :posts
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @user = User.create
|
|
30
|
+
# @post = @user.posts.create
|
|
31
|
+
# @user.posts.all == [@post] # => true
|
|
32
|
+
#
|
|
33
|
+
# _Note: Make sure that the target model is indexed on the foreign key. If it isn't, querying the association will raise Friendly::MissingIndex._
|
|
34
|
+
#
|
|
35
|
+
# Friendly defaults the foreign key to class_name_id just like ActiveRecord.
|
|
36
|
+
# It also converts the name of the association to the name of the target class just like ActiveRecord does.
|
|
37
|
+
#
|
|
38
|
+
# The biggest difference in semantics between Friendly's has_many and active_record's is that Friendly's just returns a Friendly::Scope object. If you want all the associated objects, you have to call #all to get them. You can also use any other Friendly::Scope method.
|
|
39
|
+
#
|
|
40
|
+
# @param [Symbol] name The name of the association and plural name of the target class.
|
|
41
|
+
# @option options [String] :class_name The name of the target class of this association if it is different than the name would imply.
|
|
42
|
+
# @option options [Symbol] :foreign_key Override the foreign key.
|
|
43
|
+
#
|
|
44
|
+
def has_many(name, options = {})
|
|
45
|
+
association_set.add(name, options)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
require 'friendly/document/mixin'
|
|
2
|
+
require 'set'
|
|
3
|
+
|
|
4
|
+
module Friendly
|
|
5
|
+
module Document
|
|
6
|
+
module Attributes
|
|
7
|
+
extend Mixin
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def attribute(name, type = nil, options = {})
|
|
11
|
+
attributes[name] = Attribute.new(self, name, type, options)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def attributes
|
|
15
|
+
@attributes ||= {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def new_without_change_tracking(attributes)
|
|
19
|
+
doc = new(attributes)
|
|
20
|
+
doc.reset_changes
|
|
21
|
+
doc
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(opts = {})
|
|
26
|
+
assign_default_values
|
|
27
|
+
self.attributes = opts
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def attributes=(attrs)
|
|
31
|
+
assert_no_duplicate_keys(attrs)
|
|
32
|
+
attrs.each { |name, value| assign(name, value) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_hash
|
|
36
|
+
Hash[*self.class.attributes.keys.map { |n| [n, send(n)] }.flatten]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def assign_default_values
|
|
40
|
+
self.class.attributes.values.each { |a| a.assign_default_value(self) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def assign(name, value)
|
|
44
|
+
send(:"#{name}=", value)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Notify the object that an attribute is about to change.
|
|
48
|
+
#
|
|
49
|
+
# @param [Symbol] attribute The name of the attribute about to change.
|
|
50
|
+
#
|
|
51
|
+
def will_change(attribute)
|
|
52
|
+
changed << attribute
|
|
53
|
+
instance_variable_set(:"@#{attribute}_was", send(attribute))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Get the original value of an attribute that has changed.
|
|
57
|
+
#
|
|
58
|
+
# @param [Symbol] attribute The name of the attribute.
|
|
59
|
+
#
|
|
60
|
+
def attribute_was(attribute)
|
|
61
|
+
instance_variable_get(:"@#{attribute}_was")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Has this attribute changed?
|
|
65
|
+
#
|
|
66
|
+
# @param [Symbol] attribute The name of the attribute.
|
|
67
|
+
#
|
|
68
|
+
def attribute_changed?(attribute)
|
|
69
|
+
changed.include?(attribute)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Have any of the attributes that are being tracked changed since last reset?
|
|
73
|
+
#
|
|
74
|
+
def changed?
|
|
75
|
+
!changed.empty?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Which attributes that are being tracked have changed since last reset?
|
|
79
|
+
#
|
|
80
|
+
def changed
|
|
81
|
+
@changed ||= Set.new
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Reset all the changes to this object.
|
|
85
|
+
#
|
|
86
|
+
def reset_changes
|
|
87
|
+
changed.each { |c| not_changed(c) }.clear
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Reset the changed-ness of one attribute.
|
|
91
|
+
#
|
|
92
|
+
def not_changed(attribute)
|
|
93
|
+
instance_variable_set(:"@#{attribute}_was", nil)
|
|
94
|
+
changed.delete(attribute)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Override #save to reset changes afterwards
|
|
98
|
+
#
|
|
99
|
+
# @override
|
|
100
|
+
#
|
|
101
|
+
def save
|
|
102
|
+
super
|
|
103
|
+
reset_changes
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
protected
|
|
107
|
+
def assert_no_duplicate_keys(hash)
|
|
108
|
+
if hash.keys.map { |k| k.to_s }.uniq.length < hash.keys.length
|
|
109
|
+
raise ArgumentError, "Duplicate keys: #{hash.inspect}"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -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
|