poro 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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