poro 0.1.0

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.
@@ -0,0 +1,153 @@
1
+ module Poro
2
+ module Util
3
+ # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
4
+ # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
5
+ # in inflections.rb.
6
+ #
7
+ # The Rails core team has stated patches for the inflections library will not be accepted
8
+ # in order to avoid breaking legacy applications which may be relying on errant inflections.
9
+ # If you discover an incorrect inflection and require it for your application, you'll need
10
+ # to correct it yourself (explained below).
11
+ module Inflector
12
+ extend self
13
+
14
+ # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
15
+ # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase.
16
+ #
17
+ # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
18
+ #
19
+ # Examples:
20
+ # "active_record".camelize # => "ActiveRecord"
21
+ # "active_record".camelize(:lower) # => "activeRecord"
22
+ # "active_record/errors".camelize # => "ActiveRecord::Errors"
23
+ # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
24
+ #
25
+ # As a rule of thumb you can think of +camelize+ as the inverse of +underscore+,
26
+ # though there are cases where that does not hold:
27
+ #
28
+ # "SSLError".underscore.camelize # => "SslError"
29
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
30
+ if first_letter_in_uppercase
31
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
32
+ else
33
+ lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1]
34
+ end
35
+ end
36
+
37
+ # Makes an underscored, lowercase form from the expression in the string.
38
+ #
39
+ # Changes '::' to '/' to convert namespaces to paths.
40
+ #
41
+ # Examples:
42
+ # "ActiveRecord".underscore # => "active_record"
43
+ # "ActiveRecord::Errors".underscore # => active_record/errors
44
+ #
45
+ # As a rule of thumb you can think of +underscore+ as the inverse of +camelize+,
46
+ # though there are cases where that does not hold:
47
+ #
48
+ # "SSLError".underscore.camelize # => "SslError"
49
+ def underscore(camel_cased_word)
50
+ word = camel_cased_word.to_s.dup
51
+ word.gsub!(/::/, '/')
52
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
53
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
54
+ word.tr!("-", "_")
55
+ word.downcase!
56
+ word
57
+ end
58
+
59
+ # Replaces underscores with dashes in the string.
60
+ #
61
+ # Example:
62
+ # "puni_puni" # => "puni-puni"
63
+ def dasherize(underscored_word)
64
+ underscored_word.gsub(/_/, '-')
65
+ end
66
+
67
+ # Removes the module part from the expression in the string.
68
+ #
69
+ # Examples:
70
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
71
+ # "Inflections".demodulize # => "Inflections"
72
+ def demodulize(class_name_in_module)
73
+ class_name_in_module.to_s.gsub(/^.*::/, '')
74
+ end
75
+
76
+ # Creates a foreign key name from a class name.
77
+ # +separate_class_name_and_id_with_underscore+ sets whether
78
+ # the method should put '_' between the name and 'id'.
79
+ #
80
+ # Examples:
81
+ # "Message".foreign_key # => "message_id"
82
+ # "Message".foreign_key(false) # => "messageid"
83
+ # "Admin::Post".foreign_key # => "post_id"
84
+ def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
85
+ underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
86
+ end
87
+
88
+ # Ruby 1.9 introduces an inherit argument for Module#const_get and
89
+ # #const_defined? and changes their default behavior.
90
+ if Module.method(:const_get).arity == 1
91
+ # Tries to find a constant with the name specified in the argument string:
92
+ #
93
+ # "Module".constantize # => Module
94
+ # "Test::Unit".constantize # => Test::Unit
95
+ #
96
+ # The name is assumed to be the one of a top-level constant, no matter whether
97
+ # it starts with "::" or not. No lexical context is taken into account:
98
+ #
99
+ # C = 'outside'
100
+ # module M
101
+ # C = 'inside'
102
+ # C # => 'inside'
103
+ # "C".constantize # => 'outside', same as ::C
104
+ # end
105
+ #
106
+ # NameError is raised when the name is not in CamelCase or the constant is
107
+ # unknown.
108
+ def constantize(camel_cased_word)
109
+ names = camel_cased_word.split('::')
110
+ names.shift if names.empty? || names.first.empty?
111
+
112
+ constant = Object
113
+ names.each do |name|
114
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
115
+ end
116
+ constant
117
+ end
118
+ else
119
+ def constantize(camel_cased_word) #:nodoc:
120
+ names = camel_cased_word.split('::')
121
+ names.shift if names.empty? || names.first.empty?
122
+
123
+ constant = Object
124
+ names.each do |name|
125
+ constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
126
+ end
127
+ constant
128
+ end
129
+ end
130
+
131
+ # Turns a number into an ordinal string used to denote the position in an
132
+ # ordered sequence such as 1st, 2nd, 3rd, 4th.
133
+ #
134
+ # Examples:
135
+ # ordinalize(1) # => "1st"
136
+ # ordinalize(2) # => "2nd"
137
+ # ordinalize(1002) # => "1002nd"
138
+ # ordinalize(1003) # => "1003rd"
139
+ def ordinalize(number)
140
+ if (11..13).include?(number.to_i % 100)
141
+ "#{number}th"
142
+ else
143
+ case number.to_i % 10
144
+ when 1; "#{number}st"
145
+ when 2; "#{number}nd"
146
+ when 3; "#{number}rd"
147
+ else "#{number}th"
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,66 @@
1
+ module Poro
2
+ module Util
3
+ module ModuleFinder
4
+
5
+ # Finds the given module by string.
6
+ #
7
+ # Arguments:
8
+ # [arg] The module/class name to find.
9
+ # [relative_root] If given, tries to find the requested class/module
10
+ # within this module/class. May be a string.
11
+ # [strict] Normally--mostly like Ruby--a top-level class will be returned
12
+ # at any step if no module/class matches in the namespace it is
13
+ # searching. If this is true, then a last check is made to see
14
+ # if the returned module/class is actually inside of the relative
15
+ # root.
16
+ #
17
+ # If given a class, it returns it directly. TODO: Make it look it up if
18
+ # relative_root is not Module or Object.
19
+ def self.find(arg, relative_root=Module, strict=false)
20
+ # If the argument is a kind of class, just return right away.
21
+ return arg if arg.kind_of?(Module) || arg.kind_of?(Class)
22
+
23
+ # Now we need to treat it as a string:
24
+ arg = arg.to_s
25
+ raise NameError, "Could not find a module or class from nothing." if arg.nil? || arg.empty?
26
+
27
+ # First, define the recursive function.
28
+ recursive_resolve = lambda do |curr_mod, names|
29
+ head, *rest = names.to_a
30
+ if( head.nil? && rest.empty? )
31
+ curr_mod
32
+ elsif( !(head.to_s.empty?) && curr_mod.respond_to?(:const_defined?) && curr_mod.const_defined?(head) )
33
+ recursive_resolve.call( curr_mod.const_get(head), rest)
34
+ else
35
+ raise NameError, "Could not find a module or class from #{arg.inspect}"
36
+ end
37
+ end
38
+
39
+ # If starting with ::, then the constant is aboslutely referenced.
40
+ relative_root = Module if arg[0,2]=='::'
41
+
42
+ # Split into names
43
+ start_index = arg[0,2]=='::' ? 2 : 0
44
+ mod_names = arg[start_index..-1].split('::')
45
+
46
+ # Now get the module or class.
47
+ relative_root = self.send(__method__, relative_root)
48
+ mod = recursive_resolve.call(relative_root, mod_names)
49
+
50
+ # Now, if we are strict, verify it is of the type we are looking for.
51
+ if (relative_root!=Module || relative_root!=Object) && !(mod.name.include?(relative_root.name))
52
+ base_message = "Could not find a module or class #{mod.name.inspect} inside of #{relative_root.name.inspect}"
53
+ if( strict )
54
+ raise NameError, base_message
55
+ else
56
+ STDERR.puts "WARNING: #{base_message}, top level mod/class used instead."
57
+ end
58
+ end
59
+
60
+ # Return
61
+ return mod
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,71 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "ContextFactory" do
4
+
5
+ before(:all) do
6
+ @context_manager_klass = Poro::ContextFactory
7
+
8
+ @klass_one = Class.new(Object)
9
+ @klass_two = Class.new(String)
10
+ end
11
+
12
+ it 'should save the context instance' do
13
+ testObject = @context_manager_klass.new
14
+ @context_manager_klass.instance = testObject
15
+ @context_manager_klass.instance.should == testObject
16
+ end
17
+
18
+ it 'should error if the application context manager is set to an inappropriate object kind' do
19
+ lambda {@context_manager_klass.instance = :foo}.should raise_error
20
+ lambda {@context_manager_klass.instance = nil}.should_not raise_error
21
+ end
22
+
23
+ it 'should error if the application context manager is unset' do
24
+ @context_manager_klass.instance = nil
25
+ lambda {@context_manager_klass.instance}.should raise_error
26
+ end
27
+
28
+ it 'should run the context block on fetch' do
29
+ @context_manager_klass.instance = manager = @context_manager_klass.new do |klass|
30
+ if( klass == @klass_one )
31
+ :alpha
32
+ else
33
+ :beta
34
+ end
35
+ end
36
+
37
+ @klass_one.send(:include, Poro::Persistify)
38
+ @klass_two.send(:include, Poro::Persistify)
39
+
40
+ manager.fetch(@klass_one).should == :alpha
41
+ manager.fetch(@klass_two).should == :beta
42
+ end
43
+
44
+ it 'should cache the fetched result' do
45
+ @context_manager_klass.instance = manager = @context_manager_klass.new do |klass|
46
+ o = Object.new
47
+ o.instance_variable_set(:@klass, klass)
48
+ o
49
+ end
50
+
51
+ @klass_one.send(:include, Poro::Persistify)
52
+ @klass_two.send(:include, Poro::Persistify)
53
+
54
+ context_one = manager.fetch(@klass_one)
55
+ context_two = manager.fetch(@klass_two)
56
+
57
+ manager.fetch(@klass_one).should == context_one
58
+ manager.fetch(@klass_two).should_not == context_one
59
+ manager.fetch(@klass_two).should == context_two
60
+ end
61
+
62
+ it 'should error when a class is not persistable' do
63
+ manager = @context_manager_klass.new do |klass|
64
+ Object.new
65
+ end
66
+
67
+ lambda {manager.fetch(@klass_one)}.should_not raise_error
68
+ lambda {manager.fetch(Object)}.should raise_error(Poro::ContextFactory::FactoryError)
69
+ end
70
+
71
+ end
@@ -0,0 +1,110 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "Context" do
4
+
5
+ before(:all) do
6
+ @context_klass = Poro::Context
7
+
8
+ @klass_one = Class.new(Object)
9
+ end
10
+
11
+ it 'should know its class' do
12
+ context = @context_klass.new(Object)
13
+ context.klass.should == Object
14
+ end
15
+
16
+ it 'should have an immutable class' do
17
+ context = @context_klass.new(Object)
18
+ context.should_not respond_to(:klass=)
19
+ end
20
+
21
+ it 'should return its data store' do
22
+ context = @context_klass.new(Object)
23
+ context.should respond_to(:data_store)
24
+ end
25
+
26
+ it 'should have an immutable data store' do
27
+ context = @context_klass.new(Object)
28
+ context.should_not respond_to(:dat_store=)
29
+ end
30
+
31
+ it 'should return the saved object from a save' do
32
+ context = @context_klass.new(Object)
33
+ obj = Object.new
34
+ context.save(obj).should == obj
35
+ end
36
+
37
+ it 'should return the removed object from a remove' do
38
+ context = @context_klass.new(Object)
39
+ obj = Object.new
40
+ context.remove(obj).should == obj
41
+ end
42
+
43
+ it 'should set the id on a save and unset it on remove' do
44
+ obj = Object.new
45
+ class << obj
46
+ attr_reader :id
47
+ attr_writer :id
48
+ end
49
+
50
+ obj.should respond_to(:id)
51
+ obj.should respond_to(:id=)
52
+
53
+ context = @context_klass.new(obj.class)
54
+
55
+ obj.id.should == nil
56
+ context.save(obj)
57
+ obj.id.should_not == nil
58
+ context.remove(obj)
59
+ obj.id.should == nil
60
+ end
61
+
62
+ it 'should have a customizable id method' do
63
+ obj = Object.new
64
+ class << obj
65
+ attr_reader :pk
66
+ attr_writer :pk
67
+ end
68
+
69
+ context = @context_klass.new(obj.class)
70
+ context.primary_key = :pk
71
+
72
+ obj.pk.should be_nil
73
+ context.primary_key_value(obj).should == obj.pk
74
+ context.set_primary_key_value(obj, 12345)
75
+ obj.pk.should == 12345
76
+ context.primary_key_value(obj).should == obj.pk
77
+ end
78
+
79
+ it 'should yield self at end of initialization' do
80
+ block_context = nil
81
+ context = @context_klass.new(Object) do |c|
82
+ c.klass.should == Object
83
+ block_context = c
84
+ end
85
+ context.should == block_context
86
+ end
87
+
88
+ it 'should be able to fetch the context for a class' do
89
+ x = rand(1000)
90
+ Poro::ContextFactory.instance = Poro::ContextFactory.new do |klass|
91
+ "#{klass}, #{x}"
92
+ end
93
+
94
+ @klass_one.send(:include, Poro::Persistify)
95
+
96
+ Poro::Context.fetch(@klass_one).should == "#{@klass_one}, #{x}"
97
+ end
98
+
99
+ it 'should be able to fetch the context for an object' do
100
+ x = rand(1000)
101
+ Poro::ContextFactory.instance = Poro::ContextFactory.new do |klass|
102
+ "#{klass}, #{x}"
103
+ end
104
+
105
+ @klass_one.send(:include, Poro::Persistify)
106
+
107
+ Poro::Context.fetch(@klass_one.new).should == "#{@klass_one}, #{x}"
108
+ end
109
+
110
+ end
@@ -0,0 +1,231 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "HashContext" do
4
+
5
+ before(:each) do
6
+ @obj = Object.new
7
+ class << @obj
8
+ attr_reader :id
9
+ attr_writer :id
10
+ end
11
+
12
+ @context = Poro::Contexts::HashContext.new(@obj.class)
13
+ end
14
+
15
+ it "should have a hash for a data store" do
16
+ @context.data_store.should be_kind_of(Hash)
17
+ end
18
+
19
+ it "should save and fetch a new item" do
20
+ @obj.id.should be_nil
21
+ @context.save(@obj)
22
+ @obj.id.should_not be_nil
23
+
24
+ fetched_obj = @context.fetch(@obj.id)
25
+ fetched_obj.should == @obj
26
+ end
27
+
28
+ it "should update an existing item" do
29
+ # Assume saving a new object works fir this test (there is another test save on new)
30
+ @context.save(@obj)
31
+ id_1 = @obj.id
32
+ id_1.should_not be_nil
33
+
34
+ @context.save(@obj)
35
+ id_2 = @obj.id
36
+ id_2.should == id_1
37
+ end
38
+
39
+ it "should remove an item" do
40
+ @context.save(@obj)
41
+
42
+ obj_id = @obj.id
43
+ obj_id.should_not be_nil
44
+
45
+ fetched_obj = @context.fetch(obj_id)
46
+ fetched_obj.should_not be_nil
47
+
48
+ @context.remove(@obj)
49
+
50
+ new_obj_id = @obj.id
51
+ new_obj_id.should be_nil
52
+
53
+ refetched_obj = @context.fetch(obj_id)
54
+ refetched_obj.should be_nil
55
+ end
56
+
57
+ it "should error when trying to save an object that can't handle IDs" do
58
+ o = Object.new
59
+ o.should_not respond_to(:id)
60
+ o.should_not respond_to(:id=)
61
+
62
+ lambda {@context.save(o)}.should raise_error
63
+ end
64
+
65
+ it "should error when trying to remove an object that can't handle IDs" do
66
+ o = Object.new
67
+ o.should_not respond_to(:id)
68
+ o.should_not respond_to(:id=)
69
+
70
+ lambda {@context.remove(o)}.should raise_error
71
+ end
72
+
73
+ describe "Finding" do
74
+
75
+ before(:each) do
76
+
77
+ class HashContextPerson
78
+ include Poro::Persistify
79
+ def initialize(id, first_name, last_name, friends)
80
+ @id = id
81
+ @first_name = first_name
82
+ @last_name = last_name
83
+ @friends = friends
84
+ end
85
+ end
86
+
87
+ george_smith = HashContextPerson.new(1, 'George', 'Smith', [])
88
+ george_archer = HashContextPerson.new(2, 'George', 'Archer', [george_smith])
89
+ bridgette_smith = {'id' => 3, 'first_name' => 'Bridgette', 'last_name' => 'Smith'}
90
+ karen_zeta = {:id => 4, :first_name => 'Karen', :last_name => 'Zeta', :friends => [george_archer, george_smith]}
91
+ @data = [
92
+ george_smith,
93
+ george_archer,
94
+ bridgette_smith,
95
+ karen_zeta
96
+ ]
97
+
98
+ @context = Poro::Contexts::HashContext.new(HashContextPerson)
99
+
100
+ # Cheat to jam the data in there:
101
+ @context.data_store = @data
102
+ end
103
+
104
+ it 'should get shallow values' do
105
+ expected_first_names = ['George', 'George', 'Bridgette', 'Karen']
106
+ first_names_sym = @data.map {|record| @context.send(:value_for_key, record, :first_name)[:value]}
107
+ first_names_str = @data.map {|record| @context.send(:value_for_key, record, 'first_name')[:value]}
108
+ first_names_sym.should == first_names_str
109
+ first_names_sym.should == expected_first_names
110
+
111
+ expected_ids = [1, 2, 3, 4]
112
+ ids_sym = @data.map {|record| @context.send(:value_for_key, record, :id)[:value]}
113
+ ids_str = @data.map {|record| @context.send(:value_for_key, record, 'id')[:value]}
114
+ ids_sym.should == ids_str
115
+ ids_sym.should == expected_ids
116
+ end
117
+
118
+ it 'should get embedded' do
119
+ expected_values = [{:found=>false, :value=>nil}, {:found=>true, :value=>1}, {:found=>false, :value=>nil}, {:found=>true, :value=>2}]
120
+ values = @data.map {|record| @context.send(:value_for_key, record, 'friends.0.id')}
121
+ values.should == expected_values
122
+ end
123
+
124
+ it 'should sort' do
125
+ order_opt = {:first_name => :asc}
126
+ expected_values = [3,1,2,4]
127
+ values = @context.send(:sort, @data, order_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
128
+ values.should == expected_values
129
+
130
+ order_opt = {:first_name => :asc, :last_name => :asc}
131
+ expected_values = [3,2,1,4]
132
+ values = @context.send(:sort, @data, order_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
133
+ values.should == expected_values
134
+
135
+ order_opt = {:first_name => :asc, :last_name => :desc}
136
+ expected_values = [3,1,2,4]
137
+ values = @context.send(:sort, @data, order_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
138
+ values.should == expected_values
139
+
140
+ order_opt = {:last_name => :asc, :first_name => :asc}
141
+ expected_values = [2,3,1,4]
142
+ values = @context.send(:sort, @data, order_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
143
+ values.should == expected_values
144
+
145
+ order_opt = {'friends.0.id' => :asc, 'last_name' => :desc, 'first_name' => :desc}
146
+ expected_values = [1,3,2,4]
147
+ values = @context.send(:sort, @data, order_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
148
+ values.should == expected_values
149
+ end
150
+
151
+ it 'should filter' do
152
+ filter_opt = {:last_name => 'Smith'}
153
+ expected_values = [1,3]
154
+ values = @context.send(:filter, @data, filter_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
155
+ values.should == expected_values
156
+
157
+ filter_opt = {'first_name' => 'George'}
158
+ expected_values = [1,2]
159
+ values = @context.send(:filter, @data, filter_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
160
+ values.should == expected_values
161
+
162
+ filter_opt = {:first_name => 'George', 'last_name' => 'Smith'}
163
+ expected_values = [1]
164
+ values = @context.send(:filter, @data, filter_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
165
+ values.should == expected_values
166
+
167
+ filter_opt = {'friends.0.id' => nil}
168
+ expected_values = []
169
+ values = @context.send(:filter, @data, filter_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
170
+ values.should == expected_values
171
+
172
+ filter_opt = {'friends.0.id' => 2}
173
+ expected_values = [4]
174
+ values = @context.send(:filter, @data, filter_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
175
+ values.should == expected_values
176
+ end
177
+
178
+ it 'should limit' do
179
+ limit_opt = {:limit => nil, :offset => 0}
180
+ expected_values = [1,2,3,4]
181
+ values = @context.send(:limit, @data, limit_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
182
+ values.should == expected_values
183
+
184
+ limit_opt = {:limit => 2, :offset => 0}
185
+ expected_values = [1,2]
186
+ values = @context.send(:limit, @data, limit_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
187
+ values.should == expected_values
188
+
189
+ limit_opt = {:limit => 2, :offset => 3}
190
+ expected_values = [4]
191
+ values = @context.send(:limit, @data, limit_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
192
+ values.should == expected_values
193
+
194
+ limit_opt = {:limit => 100, :offset => 2}
195
+ expected_values = [3,4]
196
+ values = @context.send(:limit, @data, limit_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
197
+ values.should == expected_values
198
+
199
+ limit_opt = {:limit => nil, :offset => 2}
200
+ expected_values = [3,4]
201
+ values = @context.send(:limit, @data, limit_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
202
+ values.should == expected_values
203
+
204
+ limit_opt = {:limit => nil, :offset => 100}
205
+ expected_values = []
206
+ values = @context.send(:limit, @data, limit_opt).map {|record| @context.send(:value_for_key, record, :id)[:value]}
207
+ values.should == expected_values
208
+ end
209
+
210
+ it 'should find all' do
211
+ opts = {:conditions => {:last_name => 'Smith'}, :order => :first_name}
212
+ expected_values = [3,1]
213
+ values = @context.find(:all, opts).map {|record| @context.send(:value_for_key, record, :id)[:value]}
214
+ values.should == expected_values
215
+
216
+ values = @context.find(:many, opts).map {|record| @context.send(:value_for_key, record, :id)[:value]}
217
+ values.should == expected_values
218
+ end
219
+
220
+ it 'should find first' do
221
+ opts = {:conditions => {:last_name => 'Smith'}, :order => :first_name}
222
+ record = @context.find(:first, opts)
223
+ @context.send(:value_for_key, record, :id)[:value].should == 3
224
+
225
+ record = @context.find(:one, opts)
226
+ @context.send(:value_for_key, record, :id)[:value].should == 3
227
+ end
228
+
229
+ end
230
+
231
+ end