friendly 0.4.3 → 0.4.4
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/CHANGELOG.md +5 -0
- data/VERSION +1 -1
- data/friendly.gemspec +12 -8
- data/lib/friendly/attribute.rb +9 -2
- data/lib/friendly/data_store.rb +2 -1
- data/lib/friendly/document.rb +11 -198
- 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/translator.rb +2 -1
- data/spec/integration/default_value_spec.rb +22 -7
- data/spec/integration/dirty_tracking_spec.rb +43 -0
- data/spec/integration/finder_spec.rb +7 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/attribute_spec.rb +28 -8
- data/spec/unit/data_store_spec.rb +13 -0
- data/spec/unit/document/attributes_spec.rb +130 -0
- data/spec/unit/document_spec.rb +0 -40
- data/spec/unit/translator_spec.rb +7 -12
- metadata +12 -8
- data/lib/friendly/config.rb +0 -5
- data/lib/friendly/named_scope.rb +0 -17
- data/spec/unit/config_spec.rb +0 -4
- data/spec/unit/named_scope_spec.rb +0 -16
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
### (0.4.4)
|
|
5
|
+
|
|
6
|
+
* (jamesgolick) Make it possible to query with order, but no conditions.
|
|
7
|
+
* (jamesgolick) Add change tracking. This is mostly to facilitate arbitrary caches.
|
|
8
|
+
|
|
4
9
|
### 0.4.2
|
|
5
10
|
|
|
6
11
|
* (nullstyle) convert UUID to SQL::Blob so that Sequel can properly escape it in databases that don't treat binary strings like regular strings.
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.4.
|
|
1
|
+
0.4.4
|
data/friendly.gemspec
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |s|
|
|
7
7
|
s.name = %q{friendly}
|
|
8
|
-
s.version = "0.4.
|
|
8
|
+
s.version = "0.4.4"
|
|
9
9
|
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
11
|
s.authors = ["James Golick"]
|
|
12
|
-
s.date = %q{
|
|
12
|
+
s.date = %q{2010-01-16}
|
|
13
13
|
s.description = %q{}
|
|
14
14
|
s.email = %q{jamesgolick@gmail.com}
|
|
15
15
|
s.extra_rdoc_files = [
|
|
@@ -37,13 +37,17 @@ Gem::Specification.new do |s|
|
|
|
37
37
|
"lib/friendly/boolean.rb",
|
|
38
38
|
"lib/friendly/cache.rb",
|
|
39
39
|
"lib/friendly/cache/by_id.rb",
|
|
40
|
-
"lib/friendly/config.rb",
|
|
41
40
|
"lib/friendly/data_store.rb",
|
|
42
41
|
"lib/friendly/document.rb",
|
|
42
|
+
"lib/friendly/document/associations.rb",
|
|
43
|
+
"lib/friendly/document/attributes.rb",
|
|
44
|
+
"lib/friendly/document/convenience.rb",
|
|
45
|
+
"lib/friendly/document/mixin.rb",
|
|
46
|
+
"lib/friendly/document/scoping.rb",
|
|
47
|
+
"lib/friendly/document/storage.rb",
|
|
43
48
|
"lib/friendly/document_table.rb",
|
|
44
49
|
"lib/friendly/index.rb",
|
|
45
50
|
"lib/friendly/memcached.rb",
|
|
46
|
-
"lib/friendly/named_scope.rb",
|
|
47
51
|
"lib/friendly/newrelic.rb",
|
|
48
52
|
"lib/friendly/query.rb",
|
|
49
53
|
"lib/friendly/scope.rb",
|
|
@@ -71,6 +75,7 @@ Gem::Specification.new do |s|
|
|
|
71
75
|
"spec/integration/convenience_api_spec.rb",
|
|
72
76
|
"spec/integration/count_spec.rb",
|
|
73
77
|
"spec/integration/default_value_spec.rb",
|
|
78
|
+
"spec/integration/dirty_tracking_spec.rb",
|
|
74
79
|
"spec/integration/find_via_cache_spec.rb",
|
|
75
80
|
"spec/integration/finder_spec.rb",
|
|
76
81
|
"spec/integration/has_many_spec.rb",
|
|
@@ -87,14 +92,13 @@ Gem::Specification.new do |s|
|
|
|
87
92
|
"spec/unit/attribute_spec.rb",
|
|
88
93
|
"spec/unit/cache_by_id_spec.rb",
|
|
89
94
|
"spec/unit/cache_spec.rb",
|
|
90
|
-
"spec/unit/config_spec.rb",
|
|
91
95
|
"spec/unit/data_store_spec.rb",
|
|
96
|
+
"spec/unit/document/attributes_spec.rb",
|
|
92
97
|
"spec/unit/document_spec.rb",
|
|
93
98
|
"spec/unit/document_table_spec.rb",
|
|
94
99
|
"spec/unit/friendly_spec.rb",
|
|
95
100
|
"spec/unit/index_spec.rb",
|
|
96
101
|
"spec/unit/memcached_spec.rb",
|
|
97
|
-
"spec/unit/named_scope_spec.rb",
|
|
98
102
|
"spec/unit/query_spec.rb",
|
|
99
103
|
"spec/unit/scope_proxy_spec.rb",
|
|
100
104
|
"spec/unit/scope_spec.rb",
|
|
@@ -164,6 +168,7 @@ Gem::Specification.new do |s|
|
|
|
164
168
|
"spec/integration/convenience_api_spec.rb",
|
|
165
169
|
"spec/integration/count_spec.rb",
|
|
166
170
|
"spec/integration/default_value_spec.rb",
|
|
171
|
+
"spec/integration/dirty_tracking_spec.rb",
|
|
167
172
|
"spec/integration/find_via_cache_spec.rb",
|
|
168
173
|
"spec/integration/finder_spec.rb",
|
|
169
174
|
"spec/integration/has_many_spec.rb",
|
|
@@ -179,14 +184,13 @@ Gem::Specification.new do |s|
|
|
|
179
184
|
"spec/unit/attribute_spec.rb",
|
|
180
185
|
"spec/unit/cache_by_id_spec.rb",
|
|
181
186
|
"spec/unit/cache_spec.rb",
|
|
182
|
-
"spec/unit/config_spec.rb",
|
|
183
187
|
"spec/unit/data_store_spec.rb",
|
|
188
|
+
"spec/unit/document/attributes_spec.rb",
|
|
184
189
|
"spec/unit/document_spec.rb",
|
|
185
190
|
"spec/unit/document_table_spec.rb",
|
|
186
191
|
"spec/unit/friendly_spec.rb",
|
|
187
192
|
"spec/unit/index_spec.rb",
|
|
188
193
|
"spec/unit/memcached_spec.rb",
|
|
189
|
-
"spec/unit/named_scope_spec.rb",
|
|
190
194
|
"spec/unit/query_spec.rb",
|
|
191
195
|
"spec/unit/scope_proxy_spec.rb",
|
|
192
196
|
"spec/unit/scope_spec.rb",
|
data/lib/friendly/attribute.rb
CHANGED
|
@@ -59,18 +59,25 @@ module Friendly
|
|
|
59
59
|
nil
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
|
+
|
|
63
|
+
def assign_default_value(document)
|
|
64
|
+
document.send(:"#{name}=", default)
|
|
65
|
+
end
|
|
62
66
|
|
|
63
67
|
protected
|
|
64
68
|
def build_accessors
|
|
65
69
|
n = name
|
|
66
70
|
klass.class_eval do
|
|
71
|
+
attr_reader n, :"#{n}_was"
|
|
72
|
+
|
|
67
73
|
eval <<-__END__
|
|
68
74
|
def #{n}=(value)
|
|
75
|
+
will_change(:#{n})
|
|
69
76
|
@#{n} = self.class.attributes[:#{n}].typecast(value)
|
|
70
77
|
end
|
|
71
78
|
|
|
72
|
-
def #{n}
|
|
73
|
-
|
|
79
|
+
def #{n}_changed?
|
|
80
|
+
attribute_changed?(:#{n})
|
|
74
81
|
end
|
|
75
82
|
__END__
|
|
76
83
|
end
|
data/lib/friendly/data_store.rb
CHANGED
|
@@ -12,7 +12,8 @@ module Friendly
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def all(persistable, query)
|
|
15
|
-
filtered = dataset(persistable)
|
|
15
|
+
filtered = dataset(persistable)
|
|
16
|
+
filtered = filtered.where(query.conditions) unless query.conditions.empty?
|
|
16
17
|
if query.limit || query.offset
|
|
17
18
|
filtered = filtered.limit(query.limit, query.offset)
|
|
18
19
|
end
|
data/lib/friendly/document.rb
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
require 'active_support/inflector'
|
|
2
|
-
require 'friendly/associations'
|
|
2
|
+
require 'friendly/document/associations'
|
|
3
|
+
require 'friendly/document/attributes'
|
|
4
|
+
require 'friendly/document/convenience'
|
|
5
|
+
require 'friendly/document/scoping'
|
|
6
|
+
require 'friendly/document/storage'
|
|
3
7
|
|
|
4
8
|
module Friendly
|
|
5
9
|
module Document
|
|
@@ -26,198 +30,18 @@ module Friendly
|
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
module ClassMethods
|
|
29
|
-
attr_writer :
|
|
30
|
-
:table_name, :collection_klass,
|
|
31
|
-
:scope_proxy, :association_set
|
|
32
|
-
|
|
33
|
-
def create_tables!
|
|
34
|
-
storage_proxy.create_tables!
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def attribute(name, type = nil, options = {})
|
|
38
|
-
attributes[name] = Attribute.new(self, name, type, options)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def storage_proxy
|
|
42
|
-
@storage_proxy ||= StorageProxy.new(self)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def query_klass
|
|
46
|
-
@query_klass ||= Query
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def collection_klass
|
|
50
|
-
@collection_klass ||= WillPaginate::Collection
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def indexes(*args)
|
|
54
|
-
storage_proxy.add(args)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def caches_by(*fields)
|
|
58
|
-
options = fields.last.is_a?(Hash) ? fields.pop : {}
|
|
59
|
-
storage_proxy.cache(fields, options)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def attributes
|
|
63
|
-
@attributes ||= {}
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def first(query)
|
|
67
|
-
storage_proxy.first(query(query))
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def all(query)
|
|
71
|
-
storage_proxy.all(query(query))
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def find(id)
|
|
75
|
-
doc = first(:id => id)
|
|
76
|
-
raise RecordNotFound, "Couldn't find #{name}/#{id}" if doc.nil?
|
|
77
|
-
doc
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def count(conditions)
|
|
81
|
-
storage_proxy.count(query(conditions))
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def paginate(conditions)
|
|
85
|
-
query = query(conditions)
|
|
86
|
-
count = count(query)
|
|
87
|
-
collection = collection_klass.new(query.page, query.per_page, count)
|
|
88
|
-
collection.replace(all(query))
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def create(attributes = {})
|
|
92
|
-
doc = new(attributes)
|
|
93
|
-
doc.save
|
|
94
|
-
doc
|
|
95
|
-
end
|
|
33
|
+
attr_writer :table_name
|
|
96
34
|
|
|
97
35
|
def table_name
|
|
98
36
|
@table_name ||= name.pluralize.underscore
|
|
99
37
|
end
|
|
100
|
-
|
|
101
|
-
def scope_proxy
|
|
102
|
-
@scope_proxy ||= ScopeProxy.new(self)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Add a named scope to this Document.
|
|
106
|
-
#
|
|
107
|
-
# e.g.
|
|
108
|
-
#
|
|
109
|
-
# class Post
|
|
110
|
-
# indexes :created_at
|
|
111
|
-
# named_scope :recent, :order! => :created_at.desc
|
|
112
|
-
# end
|
|
113
|
-
#
|
|
114
|
-
# Then, you can access the recent posts with:
|
|
115
|
-
#
|
|
116
|
-
# Post.recent.all
|
|
117
|
-
# ...or...
|
|
118
|
-
# Post.recent.first
|
|
119
|
-
#
|
|
120
|
-
# Both #all and #first also take additional parameters:
|
|
121
|
-
#
|
|
122
|
-
# Post.recent.all(:author_id => @author.id)
|
|
123
|
-
#
|
|
124
|
-
# Scopes are also chainable. See the README or Friendly::Scope docs for details.
|
|
125
|
-
#
|
|
126
|
-
# @param [Symbol] name the name of the scope.
|
|
127
|
-
# @param [Hash] parameters the query that this named scope will perform.
|
|
128
|
-
#
|
|
129
|
-
def named_scope(name, parameters)
|
|
130
|
-
scope_proxy.add_named(name, parameters)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Returns boolean based on whether the Document has a scope by a particular name.
|
|
134
|
-
#
|
|
135
|
-
# @param [Symbol] name The name of the scope in question.
|
|
136
|
-
#
|
|
137
|
-
def has_named_scope?(name)
|
|
138
|
-
scope_proxy.has_named_scope?(name)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Create an ad hoc scope on this Document.
|
|
142
|
-
#
|
|
143
|
-
# e.g.
|
|
144
|
-
#
|
|
145
|
-
# scope = Post.scope(:order! => :created_at)
|
|
146
|
-
# scope.all # => [#<Post>, #<Post>]
|
|
147
|
-
#
|
|
148
|
-
# @param [Hash] parameters the query parameters to create the scope with.
|
|
149
|
-
#
|
|
150
|
-
def scope(parameters)
|
|
151
|
-
scope_proxy.ad_hoc(parameters)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def association_set
|
|
155
|
-
@association_set ||= Associations::Set.new(self)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Add a has_many association.
|
|
159
|
-
#
|
|
160
|
-
# e.g.
|
|
161
|
-
#
|
|
162
|
-
# class Post
|
|
163
|
-
# attribute :user_id, Friendly::UUID
|
|
164
|
-
# indexes :user_id
|
|
165
|
-
# end
|
|
166
|
-
#
|
|
167
|
-
# class User
|
|
168
|
-
# has_many :posts
|
|
169
|
-
# end
|
|
170
|
-
#
|
|
171
|
-
# @user = User.create
|
|
172
|
-
# @post = @user.posts.create
|
|
173
|
-
# @user.posts.all == [@post] # => true
|
|
174
|
-
#
|
|
175
|
-
# _Note: Make sure that the target model is indexed on the foreign key. If it isn't, querying the association will raise Friendly::MissingIndex._
|
|
176
|
-
#
|
|
177
|
-
# Friendly defaults the foreign key to class_name_id just like ActiveRecord.
|
|
178
|
-
# It also converts the name of the association to the name of the target class just like ActiveRecord does.
|
|
179
|
-
#
|
|
180
|
-
# 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.
|
|
181
|
-
#
|
|
182
|
-
# @param [Symbol] name The name of the association and plural name of the target class.
|
|
183
|
-
# @option options [String] :class_name The name of the target class of this association if it is different than the name would imply.
|
|
184
|
-
# @option options [Symbol] :foreign_key Override the foreign key.
|
|
185
|
-
#
|
|
186
|
-
def has_many(name, options = {})
|
|
187
|
-
association_set.add(name, options)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
protected
|
|
191
|
-
def query(conditions)
|
|
192
|
-
conditions.is_a?(Query) ? conditions : query_klass.new(conditions)
|
|
193
|
-
end
|
|
194
38
|
end
|
|
195
39
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
assert_no_duplicate_keys(attrs)
|
|
202
|
-
attrs.each { |name, value| send("#{name}=", value) }
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
def save
|
|
206
|
-
new_record? ? storage_proxy.create(self) : storage_proxy.update(self)
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
def update_attributes(attributes)
|
|
210
|
-
self.attributes = attributes
|
|
211
|
-
save
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
def destroy
|
|
215
|
-
storage_proxy.destroy(self)
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def to_hash
|
|
219
|
-
Hash[*self.class.attributes.keys.map { |n| [n, send(n)] }.flatten]
|
|
220
|
-
end
|
|
40
|
+
include Associations
|
|
41
|
+
include Convenience
|
|
42
|
+
include Scoping
|
|
43
|
+
include Storage
|
|
44
|
+
include Attributes
|
|
221
45
|
|
|
222
46
|
def table_name
|
|
223
47
|
self.class.table_name
|
|
@@ -236,22 +60,11 @@ module Friendly
|
|
|
236
60
|
@new_record = value
|
|
237
61
|
end
|
|
238
62
|
|
|
239
|
-
def storage_proxy
|
|
240
|
-
self.class.storage_proxy
|
|
241
|
-
end
|
|
242
|
-
|
|
243
63
|
def ==(comparison_object)
|
|
244
64
|
comparison_object.equal?(self) ||
|
|
245
65
|
(comparison_object.is_a?(self.class) &&
|
|
246
66
|
!comparison_object.new_record? &&
|
|
247
67
|
comparison_object.id == id)
|
|
248
68
|
end
|
|
249
|
-
|
|
250
|
-
protected
|
|
251
|
-
def assert_no_duplicate_keys(hash)
|
|
252
|
-
if hash.keys.map { |k| k.to_s }.uniq.length < hash.keys.length
|
|
253
|
-
raise ArgumentError, "Duplicate keys: #{hash.inspect}"
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
69
|
end
|
|
257
70
|
end
|
|
@@ -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
|
data/lib/friendly/translator.rb
CHANGED
|
@@ -12,7 +12,8 @@ module Friendly
|
|
|
12
12
|
def to_object(klass, record)
|
|
13
13
|
record.delete(:added_id)
|
|
14
14
|
attributes = serializer.parse(record.delete(:attributes))
|
|
15
|
-
|
|
15
|
+
attributes.merge!(record).merge!(:new_record => false)
|
|
16
|
+
klass.new_without_change_tracking attributes
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def to_record(document)
|
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
require File.expand_path("../../spec_helper", __FILE__)
|
|
2
2
|
|
|
3
3
|
describe "An attribute with a default value" do
|
|
4
|
-
before do
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
it "has a default vaue even when it's false" do
|
|
14
|
+
@user.sad.should be_false
|
|
15
|
+
end
|
|
10
16
|
end
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
14
29
|
end
|
|
15
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
|
+
|
|
@@ -62,3 +62,10 @@ describe "limiting a query with offset" do
|
|
|
62
62
|
:order! => :created_at.desc).should == @objects.reverse.slice(2, 2)
|
|
63
63
|
end
|
|
64
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
|
data/spec/spec_helper.rb
CHANGED
data/spec/unit/attribute_spec.rb
CHANGED
|
@@ -2,12 +2,12 @@ require File.expand_path("../../spec_helper", __FILE__)
|
|
|
2
2
|
|
|
3
3
|
describe "Friendly::Attribute" do
|
|
4
4
|
before do
|
|
5
|
-
@klass = Class.new
|
|
5
|
+
@klass = Class.new { def will_change(a); end }
|
|
6
6
|
@name = Friendly::Attribute.new(@klass, :name, String)
|
|
7
7
|
@id = Friendly::Attribute.new(@klass, :id, Friendly::UUID)
|
|
8
8
|
@no_type = Friendly::Attribute.new(@klass, :no_type, nil)
|
|
9
9
|
@default = Friendly::Attribute.new(@klass, :default, String, :default => "asdf")
|
|
10
|
-
@false = Friendly::Attribute.new(@klass, :
|
|
10
|
+
@false = Friendly::Attribute.new(@klass, :false, String, :default => false)
|
|
11
11
|
@klass.stubs(:attributes).returns({:name => @name,
|
|
12
12
|
:id => @id,
|
|
13
13
|
:default => @default,
|
|
@@ -15,11 +15,27 @@ describe "Friendly::Attribute" do
|
|
|
15
15
|
@object = @klass.new
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
it "creates a
|
|
18
|
+
it "creates a getting on klass that notifies it of a change" do
|
|
19
|
+
@object.stubs(:will_change)
|
|
20
|
+
@object.name = "Something"
|
|
21
|
+
@object.should have_received(:will_change).with(:name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "creates a getter on klass" do
|
|
19
25
|
@object.name = "Something"
|
|
20
26
|
@object.name.should == "Something"
|
|
21
27
|
end
|
|
22
28
|
|
|
29
|
+
it "creates an 'attr_was' getter" do
|
|
30
|
+
@object.instance_variable_set(:@name_was, "Joe the Plumber")
|
|
31
|
+
@object.name_was.should == "Joe the Plumber"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "creates an attr_changed? query method" do
|
|
35
|
+
@object.stubs(:attribute_changed?).with(:name).returns(true)
|
|
36
|
+
@object.should be_name_changed
|
|
37
|
+
end
|
|
38
|
+
|
|
23
39
|
it "typecasts values using the converter function" do
|
|
24
40
|
uuid = Friendly::UUID.new
|
|
25
41
|
@id.typecast(uuid.to_s).should == uuid
|
|
@@ -37,10 +53,6 @@ describe "Friendly::Attribute" do
|
|
|
37
53
|
}.should raise_error(Friendly::NoConverterExists)
|
|
38
54
|
end
|
|
39
55
|
|
|
40
|
-
it "creates a getter with a default value" do
|
|
41
|
-
@object.id.should be_instance_of(Friendly::UUID)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
56
|
it "has a default value of type.new" do
|
|
45
57
|
@id.default.should be_instance_of(Friendly::UUID)
|
|
46
58
|
end
|
|
@@ -55,13 +67,21 @@ describe "Friendly::Attribute" do
|
|
|
55
67
|
|
|
56
68
|
it "can have a default value" do
|
|
57
69
|
@default.default.should == "asdf"
|
|
58
|
-
@klass.new
|
|
70
|
+
@obj = @klass.new
|
|
71
|
+
@default.assign_default_value(@obj)
|
|
72
|
+
@obj.default.should == "asdf"
|
|
59
73
|
end
|
|
60
74
|
|
|
61
75
|
it "has a default value even if it's false" do
|
|
62
76
|
@false.default.should be_false
|
|
63
77
|
end
|
|
64
78
|
|
|
79
|
+
it "knows how to assign its own default" do
|
|
80
|
+
@object = stub(:false= => nil)
|
|
81
|
+
@false.assign_default_value(@object)
|
|
82
|
+
@object.should have_received(:false=).with(false)
|
|
83
|
+
end
|
|
84
|
+
|
|
65
85
|
describe "registering a type" do
|
|
66
86
|
before do
|
|
67
87
|
@klass = Class.new
|
|
@@ -76,6 +76,19 @@ describe "Friendly::DataStore" do
|
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
+
describe "all without conditions" do
|
|
80
|
+
before do
|
|
81
|
+
@all = stub(:map => [])
|
|
82
|
+
@users.stubs(:order).with(:created_at).returns(@all)
|
|
83
|
+
@query = query(:order! => :created_at)
|
|
84
|
+
@return = @datastore.all(@klass, @query)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "orders the filtered dataset and returns the results" do
|
|
88
|
+
@return.should == []
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
79
92
|
describe "retrieving first with conditions" do
|
|
80
93
|
before do
|
|
81
94
|
@users.first = {{:id => 1} => {:id => 1}}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require File.expand_path("../../../spec_helper", __FILE__)
|
|
2
|
+
|
|
3
|
+
describe "Friendly::Document::Attributes" do
|
|
4
|
+
before do
|
|
5
|
+
@klass = Class.new do
|
|
6
|
+
include Friendly::Document::Attributes
|
|
7
|
+
|
|
8
|
+
attribute :name, String
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe "#initialize" do
|
|
13
|
+
it "sets the attributes using the setters" do
|
|
14
|
+
@doc = @klass.new :name => "Bond"
|
|
15
|
+
@doc.name.should == "Bond"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "assigns the default values" do
|
|
19
|
+
@klass.attribute :id, Friendly::UUID
|
|
20
|
+
@klass.attributes[:id] = stub(:assign_default_value => nil)
|
|
21
|
+
@klass.attributes[:name] = stub(:assign_default_value => nil,
|
|
22
|
+
:typecast => "Bond")
|
|
23
|
+
@doc = @klass.new :name => "Bond"
|
|
24
|
+
@klass.attributes[:id].should have_received(:assign_default_value).with(@doc)
|
|
25
|
+
@klass.attributes[:name].should have_received(:assign_default_value).with(@doc)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe "#attributes=" do
|
|
30
|
+
before do
|
|
31
|
+
@object = @klass.new
|
|
32
|
+
@object.attributes = {:name => "Bond"}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "sets the attributes using the setters" do
|
|
36
|
+
@object.name.should == "Bond"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "raises ArgumentError when there are duplicate keys of differing type" do
|
|
40
|
+
lambda {
|
|
41
|
+
@object.attributes = {:name => "Bond", "name" => "Bond"}
|
|
42
|
+
}.should raise_error(ArgumentError)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "#to_hash" do
|
|
47
|
+
before do
|
|
48
|
+
@object = @klass.new(:name => "Stewie")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "creates a hash that contains its attributes" do
|
|
52
|
+
@object.to_hash.should == {:name => "Stewie"}
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "#assign" do
|
|
57
|
+
before do
|
|
58
|
+
@object = @klass.new
|
|
59
|
+
@object.assign(:name, "James Bond")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "assigns the value to the attribute" do
|
|
63
|
+
@object.name.should == "James Bond"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe "#will_change" do
|
|
68
|
+
before do
|
|
69
|
+
@klass.send(:attr_accessor, :some_variable)
|
|
70
|
+
@object = @klass.new
|
|
71
|
+
@object.some_variable = "Some value"
|
|
72
|
+
@object.will_change(:some_variable)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "makes the object #changed?" do
|
|
76
|
+
@object.should be_changed
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "returns the value of the variable for #attribute_was" do
|
|
80
|
+
@object.attribute_was(:some_variable).should == "Some value"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "returns true for attribute_changed?(:some_variable)" do
|
|
84
|
+
@object.should be_attribute_changed(:some_variable)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe "#reset_changes" do
|
|
89
|
+
before do
|
|
90
|
+
@klass.send(:attr_accessor, :some_variable)
|
|
91
|
+
@object = @klass.new
|
|
92
|
+
@object.some_variable = "Some value"
|
|
93
|
+
@object.will_change(:some_variable)
|
|
94
|
+
@object.reset_changes
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "resets the changed status of the object" do
|
|
98
|
+
@object.should_not be_changed
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "returns nil for attribute_was(:some_variable)" do
|
|
102
|
+
@object.attribute_was(:some_variable).should be_nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "returns false for attribute_changed?(:some_variable)" do
|
|
106
|
+
@object.should_not be_attribute_changed(:some_variable)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe "#new_without_change_tracking" do
|
|
111
|
+
before do
|
|
112
|
+
@klass = Class.new do
|
|
113
|
+
attr_reader :name
|
|
114
|
+
|
|
115
|
+
def name=(name)
|
|
116
|
+
will_change(:name)
|
|
117
|
+
@name = name
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
include Friendly::Document::Attributes
|
|
121
|
+
end
|
|
122
|
+
@doc = @klass.new_without_change_tracking(:name => "James")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "initializes and then calls reset_changes" do
|
|
126
|
+
@doc.name.should == "James"
|
|
127
|
+
@doc.should_not be_changed
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
data/spec/unit/document_spec.rb
CHANGED
|
@@ -60,46 +60,6 @@ describe "Friendly::Document" do
|
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
describe "converting a document to a hash" do
|
|
64
|
-
before do
|
|
65
|
-
@object = @klass.new(:name => "Stewie")
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
it "creates a hash that contains its attributes" do
|
|
69
|
-
@object.to_hash.should == {:name => "Stewie",
|
|
70
|
-
:id => @object.id,
|
|
71
|
-
:created_at => @object.created_at,
|
|
72
|
-
:updated_at => @object.updated_at}
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
describe "setting the attributes all at once" do
|
|
77
|
-
before do
|
|
78
|
-
@object = @klass.new
|
|
79
|
-
@object.attributes = {:name => "Bond"}
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
it "sets the attributes using the setters" do
|
|
83
|
-
@object.name.should == "Bond"
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
it "raises ArgumentError when there are duplicate keys of differing type" do
|
|
87
|
-
lambda {
|
|
88
|
-
@object.attributes = {:name => "Bond", "name" => "Bond"}
|
|
89
|
-
}.should raise_error(ArgumentError)
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
describe "initializing a document" do
|
|
94
|
-
before do
|
|
95
|
-
@doc = @klass.new :name => "Bond"
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
it "sets the attributes using the setters" do
|
|
99
|
-
@doc.name.should == "Bond"
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
63
|
describe "table name" do
|
|
104
64
|
it "by default: is the class name, converted with pluralize.underscore" do
|
|
105
65
|
User.table_name.should == "users"
|
|
@@ -16,20 +16,15 @@ describe "Friendly::Translator" do
|
|
|
16
16
|
:created_at => @time,
|
|
17
17
|
:updated_at => @time,
|
|
18
18
|
:attributes => "THE JSON"}
|
|
19
|
-
@
|
|
20
|
-
@
|
|
19
|
+
@doc = stub
|
|
20
|
+
@klass = stub
|
|
21
|
+
@klass.stubs(:new_without_change_tracking).
|
|
22
|
+
with(:updated_at => @time, :new_record => false,
|
|
23
|
+
:name => "Stewie", :created_at => @time).returns(@doc)
|
|
21
24
|
end
|
|
22
25
|
|
|
23
|
-
it "creates a
|
|
24
|
-
@
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
it "sets updated_at" do
|
|
28
|
-
@doc.updated_at.should == @time
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
it "sets new_record to false" do
|
|
32
|
-
@doc.new_record.should be_false
|
|
26
|
+
it "creates a new object without change tracking" do
|
|
27
|
+
@translator.to_object(@klass, @row).should == @doc
|
|
33
28
|
end
|
|
34
29
|
end
|
|
35
30
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: friendly
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Golick
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date:
|
|
12
|
+
date: 2010-01-16 00:00:00 -08:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
@@ -122,13 +122,17 @@ files:
|
|
|
122
122
|
- lib/friendly/boolean.rb
|
|
123
123
|
- lib/friendly/cache.rb
|
|
124
124
|
- lib/friendly/cache/by_id.rb
|
|
125
|
-
- lib/friendly/config.rb
|
|
126
125
|
- lib/friendly/data_store.rb
|
|
127
126
|
- lib/friendly/document.rb
|
|
127
|
+
- lib/friendly/document/associations.rb
|
|
128
|
+
- lib/friendly/document/attributes.rb
|
|
129
|
+
- lib/friendly/document/convenience.rb
|
|
130
|
+
- lib/friendly/document/mixin.rb
|
|
131
|
+
- lib/friendly/document/scoping.rb
|
|
132
|
+
- lib/friendly/document/storage.rb
|
|
128
133
|
- lib/friendly/document_table.rb
|
|
129
134
|
- lib/friendly/index.rb
|
|
130
135
|
- lib/friendly/memcached.rb
|
|
131
|
-
- lib/friendly/named_scope.rb
|
|
132
136
|
- lib/friendly/newrelic.rb
|
|
133
137
|
- lib/friendly/query.rb
|
|
134
138
|
- lib/friendly/scope.rb
|
|
@@ -156,6 +160,7 @@ files:
|
|
|
156
160
|
- spec/integration/convenience_api_spec.rb
|
|
157
161
|
- spec/integration/count_spec.rb
|
|
158
162
|
- spec/integration/default_value_spec.rb
|
|
163
|
+
- spec/integration/dirty_tracking_spec.rb
|
|
159
164
|
- spec/integration/find_via_cache_spec.rb
|
|
160
165
|
- spec/integration/finder_spec.rb
|
|
161
166
|
- spec/integration/has_many_spec.rb
|
|
@@ -172,14 +177,13 @@ files:
|
|
|
172
177
|
- spec/unit/attribute_spec.rb
|
|
173
178
|
- spec/unit/cache_by_id_spec.rb
|
|
174
179
|
- spec/unit/cache_spec.rb
|
|
175
|
-
- spec/unit/config_spec.rb
|
|
176
180
|
- spec/unit/data_store_spec.rb
|
|
181
|
+
- spec/unit/document/attributes_spec.rb
|
|
177
182
|
- spec/unit/document_spec.rb
|
|
178
183
|
- spec/unit/document_table_spec.rb
|
|
179
184
|
- spec/unit/friendly_spec.rb
|
|
180
185
|
- spec/unit/index_spec.rb
|
|
181
186
|
- spec/unit/memcached_spec.rb
|
|
182
|
-
- spec/unit/named_scope_spec.rb
|
|
183
187
|
- spec/unit/query_spec.rb
|
|
184
188
|
- spec/unit/scope_proxy_spec.rb
|
|
185
189
|
- spec/unit/scope_spec.rb
|
|
@@ -271,6 +275,7 @@ test_files:
|
|
|
271
275
|
- spec/integration/convenience_api_spec.rb
|
|
272
276
|
- spec/integration/count_spec.rb
|
|
273
277
|
- spec/integration/default_value_spec.rb
|
|
278
|
+
- spec/integration/dirty_tracking_spec.rb
|
|
274
279
|
- spec/integration/find_via_cache_spec.rb
|
|
275
280
|
- spec/integration/finder_spec.rb
|
|
276
281
|
- spec/integration/has_many_spec.rb
|
|
@@ -286,14 +291,13 @@ test_files:
|
|
|
286
291
|
- spec/unit/attribute_spec.rb
|
|
287
292
|
- spec/unit/cache_by_id_spec.rb
|
|
288
293
|
- spec/unit/cache_spec.rb
|
|
289
|
-
- spec/unit/config_spec.rb
|
|
290
294
|
- spec/unit/data_store_spec.rb
|
|
295
|
+
- spec/unit/document/attributes_spec.rb
|
|
291
296
|
- spec/unit/document_spec.rb
|
|
292
297
|
- spec/unit/document_table_spec.rb
|
|
293
298
|
- spec/unit/friendly_spec.rb
|
|
294
299
|
- spec/unit/index_spec.rb
|
|
295
300
|
- spec/unit/memcached_spec.rb
|
|
296
|
-
- spec/unit/named_scope_spec.rb
|
|
297
301
|
- spec/unit/query_spec.rb
|
|
298
302
|
- spec/unit/scope_proxy_spec.rb
|
|
299
303
|
- spec/unit/scope_spec.rb
|
data/lib/friendly/config.rb
DELETED
data/lib/friendly/named_scope.rb
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
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
|
data/spec/unit/config_spec.rb
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
require File.expand_path("../../spec_helper", __FILE__)
|
|
2
|
-
|
|
3
|
-
describe "Friendly::NamedScope" do
|
|
4
|
-
before do
|
|
5
|
-
@klass = stub
|
|
6
|
-
@scope = stub
|
|
7
|
-
@scope_klass = stub
|
|
8
|
-
@parameters = {:name => "James"}
|
|
9
|
-
@scope_klass.stubs(:new).with(@klass, @parameters).returns(@scope)
|
|
10
|
-
@named_scope = Friendly::NamedScope.new(@klass, @parameters, @scope_klass)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
it "provides scope instances with the given parameters" do
|
|
14
|
-
@named_scope.scope.should == @scope
|
|
15
|
-
end
|
|
16
|
-
end
|