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.
- 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
|