poro 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +24 -0
- data/README.rdoc +215 -0
- data/Rakefile +37 -0
- data/lib/poro.rb +14 -0
- data/lib/poro/context.rb +459 -0
- data/lib/poro/context_factories.rb +1 -0
- data/lib/poro/context_factories/README.txt +8 -0
- data/lib/poro/context_factories/single_store.rb +34 -0
- data/lib/poro/context_factories/single_store/hash_factory.rb +19 -0
- data/lib/poro/context_factories/single_store/mongo_factory.rb +36 -0
- data/lib/poro/context_factory.rb +70 -0
- data/lib/poro/contexts.rb +4 -0
- data/lib/poro/contexts/hash_context.rb +177 -0
- data/lib/poro/contexts/mongo_context.rb +474 -0
- data/lib/poro/modelify.rb +100 -0
- data/lib/poro/persistify.rb +42 -0
- data/lib/poro/util.rb +2 -0
- data/lib/poro/util/inflector.rb +103 -0
- data/lib/poro/util/inflector/inflections.rb +213 -0
- data/lib/poro/util/inflector/methods.rb +153 -0
- data/lib/poro/util/module_finder.rb +66 -0
- data/spec/context_factory_spec.rb +71 -0
- data/spec/context_spec.rb +110 -0
- data/spec/hash_context_spec.rb +231 -0
- data/spec/inflector_spec.rb +32 -0
- data/spec/modelfy.rb +75 -0
- data/spec/module_finder_spec.rb +57 -0
- data/spec/mongo_context_spec.rb +28 -0
- data/spec/single_store_spec.rb +55 -0
- data/spec/spec_helper.rb +4 -0
- metadata +95 -0
@@ -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
|