armada 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +0 -0
- data/Rakefile +10 -0
- data/lib/armada/attribute_methods.rb +76 -0
- data/lib/armada/callbacks.rb +77 -0
- data/lib/armada/connection.rb +54 -0
- data/lib/armada/database_methods.rb +32 -0
- data/lib/armada/dirty.rb +35 -0
- data/lib/armada/errors.rb +19 -0
- data/lib/armada/finder_methods.rb +43 -0
- data/lib/armada/model.rb +92 -0
- data/lib/armada/observer.rb +34 -0
- data/lib/armada/relation.rb +262 -0
- data/lib/armada/timestamp.rb +58 -0
- data/lib/armada/validations.rb +41 -0
- data/lib/armada.rb +27 -0
- data/test/armada_finder_methods_spec.rb +20 -0
- data/test/armada_model_spec.rb +95 -0
- data/test/armada_relation_spec.rb +112 -0
- data/test/armada_validations_spec.rb +15 -0
- data/test/spec_helper.rb +31 -0
- metadata +129 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2010 Sam Aarons
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Armada
|
4
|
+
module AttributeMethods
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include ActiveModel::AttributeMethods
|
7
|
+
|
8
|
+
included do
|
9
|
+
attr_reader :attributes
|
10
|
+
class_attribute :columns
|
11
|
+
self.columns = [:id]
|
12
|
+
["", "="].each { |x| attribute_method_suffix(x) }
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def add_columns(*cols)
|
17
|
+
self.columns = (self.columns + cols.map(&:to_sym)).uniq
|
18
|
+
end
|
19
|
+
alias :add_column :add_columns
|
20
|
+
|
21
|
+
def remove_columns(*cols)
|
22
|
+
self.columns = (self.columns - cols.map(&:to_sym).delete_if { |x| x == :id })
|
23
|
+
end
|
24
|
+
alias :remove_column :remove_columns
|
25
|
+
|
26
|
+
def define_attribute_methods
|
27
|
+
super(self.columns)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def write_attribute(attribute_name, value)
|
32
|
+
attribute_name = attribute_name.to_s
|
33
|
+
if !persisted? || attribute_name != "id"
|
34
|
+
@attributes[attribute_name] = value
|
35
|
+
else
|
36
|
+
@attributes["id"]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def read_attribute(attribute_name)
|
41
|
+
@attributes[attribute_name]
|
42
|
+
end
|
43
|
+
|
44
|
+
def attributes=(attributes)
|
45
|
+
attributes.each_pair { |k, v| send("#{k}=",v) }
|
46
|
+
@attributes
|
47
|
+
end
|
48
|
+
|
49
|
+
def method_missing(method_id, *args, &block)
|
50
|
+
if !self.class.attribute_methods_generated?
|
51
|
+
self.class.define_attribute_methods
|
52
|
+
method_name = method_id.to_s
|
53
|
+
guard_private_attribute_method!(method_name, args)
|
54
|
+
send(method_id, *args, &block)
|
55
|
+
else
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def respond_to?(*args)
|
61
|
+
self.class.define_attribute_methods
|
62
|
+
super
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def attribute=(attribute_name, value)
|
67
|
+
write_attribute(attribute_name, value)
|
68
|
+
end
|
69
|
+
|
70
|
+
def attribute(attribute_name)
|
71
|
+
read_attribute(attribute_name)
|
72
|
+
end
|
73
|
+
alias :read_attribute_for_validation :attribute
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Armada
|
4
|
+
module Callbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
CALLBACKS = [
|
8
|
+
:before_validation, :after_validation,
|
9
|
+
:before_save, :after_save, :around_save,
|
10
|
+
:before_create, :after_create, :around_create,
|
11
|
+
:before_update, :after_update, :around_update,
|
12
|
+
:before_destroy, :after_destroy, :around_destroy
|
13
|
+
]
|
14
|
+
|
15
|
+
included do
|
16
|
+
%w(create_or_update valid? create update destroy).each do |method|
|
17
|
+
alias_method_chain method, :callbacks
|
18
|
+
end
|
19
|
+
extend ActiveModel::Callbacks
|
20
|
+
define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
|
21
|
+
define_model_callbacks :save, :create, :update, :destroy
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
def before_validation(*args, &block)
|
26
|
+
options = args.last
|
27
|
+
if options.is_a?(Hash) && options[:on]
|
28
|
+
options[:if] = Array(options[:if])
|
29
|
+
options[:if] << "@_on_validate == :#{options[:on]}"
|
30
|
+
end
|
31
|
+
set_callback(:validation, :before, *args, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def after_validation(*args, &block)
|
35
|
+
options = args.extract_options!
|
36
|
+
options[:prepend] = true
|
37
|
+
options[:if] = Array(options[:if])
|
38
|
+
options[:if] << "!halted && value != false"
|
39
|
+
options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
|
40
|
+
set_callback(:validation, :after, *(args << options), &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid_with_callbacks?
|
45
|
+
@_on_validate = new_record? ? :create : :update
|
46
|
+
_run_validation_callbacks do
|
47
|
+
valid_without_callbacks?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy_with_callbacks
|
52
|
+
_run_destroy_callbacks do
|
53
|
+
destroy_without_callbacks
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def create_or_update_with_callbacks
|
59
|
+
_run_save_callbacks do
|
60
|
+
create_or_update_without_callbacks
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_with_callbacks
|
65
|
+
_run_create_callbacks do
|
66
|
+
create_without_callbacks
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def update_with_callbacks(*args)
|
71
|
+
_run_update_callbacks do
|
72
|
+
update_without_callbacks(*args)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Armada
|
4
|
+
mattr_reader :connection
|
5
|
+
|
6
|
+
class Connection
|
7
|
+
def initialize(host, port, password)
|
8
|
+
@host = host
|
9
|
+
@port = port
|
10
|
+
@password = password
|
11
|
+
connect
|
12
|
+
end
|
13
|
+
|
14
|
+
def connect
|
15
|
+
begin
|
16
|
+
@socket = TCPSocket.new(@host, @port)
|
17
|
+
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
18
|
+
query(["auth", @password]) if @password
|
19
|
+
rescue
|
20
|
+
raise Armada::ConnectionError, "could not connect"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def query(q)
|
25
|
+
request = ActiveSupport::JSON.encode(q)
|
26
|
+
@socket.write(request << "\r\n")
|
27
|
+
status, value = ActiveSupport::JSON.decode(@socket.gets)
|
28
|
+
status == 0 ? value : raise(Armada::ServerError.new(request),value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.setup!(spec = {})
|
33
|
+
return @@connection if @@connection
|
34
|
+
config = { address: "127.0.0.1", port: 3400 }.merge!(spec)
|
35
|
+
@@connection = Connection.new(config[:address], config[:port], config[:password])
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.compact!
|
39
|
+
query_if_connection("compact")
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.list_collections
|
43
|
+
query_if_connection("list-collections")
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.explain(query)
|
47
|
+
query_if_connection("explain", query)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def self.query_if_connection(*query)
|
52
|
+
@@connection && @@connection.query(query)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Armada
|
4
|
+
module DatabaseMethods
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
singleton_class.alias_method_chain :inherited, :collection_name
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def collection_name(name = nil)
|
13
|
+
name ? (@collection_name = name) : @collection_name
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def instantiate(attributes)
|
18
|
+
record = self.allocate
|
19
|
+
record.instance_variable_set(:@attributes, attributes.with_indifferent_access)
|
20
|
+
record.instance_variable_set(:@new_record, false)
|
21
|
+
record
|
22
|
+
end
|
23
|
+
|
24
|
+
def inherited_with_collection_name(subclass)
|
25
|
+
subclass.collection_name(subclass.model_name.plural)
|
26
|
+
inherited_without_collection_name(subclass)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
data/lib/armada/dirty.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Armada
|
4
|
+
module Dirty
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include ActiveModel::Dirty
|
7
|
+
|
8
|
+
included do
|
9
|
+
[:create_or_update, :write_attribute].each do |method|
|
10
|
+
alias_method_chain method, :dirty
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def write_attribute_with_dirty(attribute_name, value)
|
16
|
+
send("#{attribute_name}_will_change!") if persisted?
|
17
|
+
write_attribute_without_dirty(attribute_name, value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_or_update_with_dirty
|
21
|
+
if status = create_or_update_without_dirty
|
22
|
+
@previously_changed = changes
|
23
|
+
changed_attributes.clear
|
24
|
+
else
|
25
|
+
changed.each { |attribute_name| send("reset_#{attribute_name}!") } if persisted?
|
26
|
+
end
|
27
|
+
status
|
28
|
+
end
|
29
|
+
|
30
|
+
def serializable_changes
|
31
|
+
changed.inject({}) { |h, a| h[a] = @attributes[a]; h }
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Armada
|
4
|
+
class ServerError < StandardError
|
5
|
+
attr_reader :query
|
6
|
+
def initialize(query)
|
7
|
+
@query = query
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class ConnectionError < StandardError
|
12
|
+
end
|
13
|
+
|
14
|
+
class RecordNotFound < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
class RecordNotSaved < StandardError
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Armada
|
4
|
+
module FinderMethods
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
def find(*args)
|
10
|
+
find_from_ids(args)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def find_from_ids(ids)
|
15
|
+
expects_array = ids.first.kind_of?(Array)
|
16
|
+
return ids.first if expects_array && ids.first.empty?
|
17
|
+
|
18
|
+
ids = ids.flatten.compact.uniq
|
19
|
+
|
20
|
+
case ids.size
|
21
|
+
when 0
|
22
|
+
raise(Armada::RecordNotFound)
|
23
|
+
when 1
|
24
|
+
result = find_one(ids.first)
|
25
|
+
expects_array ? [ result ] : result
|
26
|
+
else
|
27
|
+
find_some(ids)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_one(id)
|
32
|
+
result = self.where(:id => id).limit(1).first
|
33
|
+
result.blank? ? raise(Armada::RecordNotFound) : result
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_some(ids)
|
37
|
+
results = self.where(:id => ids).all
|
38
|
+
results.size == ids.size ? results : raise(Armada::RecordNotFound)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/armada/model.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Armada
|
4
|
+
class Model
|
5
|
+
include Armada::Validations
|
6
|
+
extend ActiveModel::Naming
|
7
|
+
include Armada::FinderMethods
|
8
|
+
include ActiveModel::Observing
|
9
|
+
include Armada::DatabaseMethods
|
10
|
+
include Armada::RelationMethods
|
11
|
+
include ActiveModel::Conversion
|
12
|
+
extend ActiveModel::Translation
|
13
|
+
include Armada::AttributeMethods
|
14
|
+
include ActiveModel::Validations
|
15
|
+
include ActiveModel::Serialization
|
16
|
+
include ActiveModel::Serializers::Xml
|
17
|
+
include ActiveModel::Serializers::JSON
|
18
|
+
|
19
|
+
def initialize(attributes = {})
|
20
|
+
@attributes = {}.with_indifferent_access
|
21
|
+
@new_record = true
|
22
|
+
self.attributes = attributes
|
23
|
+
end
|
24
|
+
|
25
|
+
def new_record?
|
26
|
+
@new_record || false
|
27
|
+
end
|
28
|
+
|
29
|
+
def destroy
|
30
|
+
@destroyed = (persisted? && relation.delete == 1)
|
31
|
+
end
|
32
|
+
|
33
|
+
def destroyed?
|
34
|
+
@destroyed || false
|
35
|
+
end
|
36
|
+
|
37
|
+
def persisted?
|
38
|
+
!(new_record? || destroyed?)
|
39
|
+
end
|
40
|
+
|
41
|
+
def save
|
42
|
+
create_or_update
|
43
|
+
end
|
44
|
+
|
45
|
+
def save!
|
46
|
+
save || raise(Armada::RecordNotSaved)
|
47
|
+
end
|
48
|
+
|
49
|
+
def ==(other)
|
50
|
+
klass = self.class
|
51
|
+
case other
|
52
|
+
when klass then klass.collection_name == other.class.collection_name && self.attributes == other.attributes
|
53
|
+
else false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
def generate_unique_id
|
59
|
+
self.id ||= rand(36**26).to_s(36)[0..24]
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def create_or_update
|
64
|
+
(valid? && !destroyed?) && (new_record? ? create : update)
|
65
|
+
end
|
66
|
+
|
67
|
+
def create
|
68
|
+
if status = (relation.insert(@attributes) == 1)
|
69
|
+
@new_record = false
|
70
|
+
end
|
71
|
+
status
|
72
|
+
end
|
73
|
+
|
74
|
+
def update
|
75
|
+
changed? && relation.update(serializable_changes) == 1
|
76
|
+
end
|
77
|
+
|
78
|
+
def relation
|
79
|
+
@relation ||= Armada::Relation.new(self.class).where(:id => self.id)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Armada::Model.class_eval do
|
86
|
+
include Armada::Dirty
|
87
|
+
include Armada::Callbacks
|
88
|
+
include Armada::Timestamp
|
89
|
+
|
90
|
+
validates :id, :presence => true
|
91
|
+
before_validation :generate_unique_id, :on => :create
|
92
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Armada
|
4
|
+
class Observer < ActiveModel::Observer
|
5
|
+
class_attribute :observed_methods
|
6
|
+
self.observed_methods = []
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super
|
10
|
+
observed_subclasses.each { |klass| add_observer!(klass) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.method_added(method)
|
14
|
+
self.observed_methods += [method] if Armada::Callbacks::CALLBACKS.include?(method.to_sym)
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
def observed_subclasses
|
19
|
+
observed_classes.sum([]) { |klass| klass.send(:subclasses) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_observer!(klass)
|
23
|
+
super
|
24
|
+
|
25
|
+
self.class.observed_methods.each do |method|
|
26
|
+
callback = :"_notify_observers_for_#{method}"
|
27
|
+
if (klass.instance_methods & [callback, callback.to_s]).empty?
|
28
|
+
klass.class_eval "def #{callback}; notify_observers(:#{method}); end"
|
29
|
+
klass.send(method, callback)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Armada
|
4
|
+
module RelationMethods
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
RELATION_METHODS = %w(all)
|
9
|
+
QUERY_OPTIONS = %w(where offset order limit only distinct)
|
10
|
+
QUERY_METHODS = %w(insert select delete count multi_read multi_write checked_write create_index drop_index list_indexes)
|
11
|
+
|
12
|
+
def relation
|
13
|
+
@_class_relation ||= Armada::Relation.new(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
delegate(*(QUERY_METHODS + QUERY_OPTIONS + RELATION_METHODS), :to => :relation)
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class Relation
|
23
|
+
FIND_OPTIONS_KEYS = %w(where offset order limit only distinct)
|
24
|
+
|
25
|
+
# undefine :select to avoid bugs/confusion with Kernel.select
|
26
|
+
# ...true story
|
27
|
+
undef_method :select
|
28
|
+
|
29
|
+
def initialize(superclass)
|
30
|
+
@superclass = superclass
|
31
|
+
@join = "and"
|
32
|
+
end
|
33
|
+
|
34
|
+
def where(conditions)
|
35
|
+
where = []
|
36
|
+
conditions.each_pair do |attribute, value|
|
37
|
+
if value.is_a?(Hash)
|
38
|
+
value.each_pair do |operator, val|
|
39
|
+
where << [operator, attribute, build_condition_value(val)]
|
40
|
+
end
|
41
|
+
else
|
42
|
+
where << build_condition(attribute, value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
where = where.size > 1 ? where.unshift("and") : where.first
|
46
|
+
|
47
|
+
join, old_where = [@join, @where]
|
48
|
+
|
49
|
+
new_where = if option_defined?(:where)
|
50
|
+
if join == old_where.first && old_where.first == where.first
|
51
|
+
old_where.concat(where.from(1))
|
52
|
+
elsif join == old_where.first && !is_conjunction?(where.first)
|
53
|
+
old_where << where
|
54
|
+
elsif join == where.first && is_conjunction?(old_where.first)
|
55
|
+
old_where.tap { |w| w[-1] = ([join] << w.last).concat(where.from(1)) }
|
56
|
+
elsif join == where.first && !is_conjunction?(old_where.first)
|
57
|
+
where.insert(1,old_where)
|
58
|
+
elsif join == "and" && old_where.first == "or" && !is_conjunction?(where.first) && !is_conjunction?(old_where.last.first)
|
59
|
+
old_where.tap { |w| w[-1] = [join] << w.last << where }
|
60
|
+
elsif join == "and" && old_where.first == "or" && !is_conjunction?(where.first) && is_conjunction?(old_where.last.first)
|
61
|
+
old_where.tap { |w| w[-1] << where }
|
62
|
+
else
|
63
|
+
[join] << old_where << where
|
64
|
+
end
|
65
|
+
else
|
66
|
+
where
|
67
|
+
end
|
68
|
+
self.dup.tap do |r|
|
69
|
+
r.set_option(:where, new_where)
|
70
|
+
r.set_option(:join, "and")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def or
|
75
|
+
raise(ArgumentError, "Missing 'where' condition") unless option_defined?(:where)
|
76
|
+
self.dup.tap { |r| r.set_option(:join, "or") }
|
77
|
+
end
|
78
|
+
|
79
|
+
def offset(value)
|
80
|
+
self.dup.tap { |r| r.set_option(:offset, value) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def limit(value)
|
84
|
+
self.dup.tap { |r| r.set_option(:limit, value) }
|
85
|
+
end
|
86
|
+
|
87
|
+
def order(*attributes)
|
88
|
+
options = attributes.extract_options!
|
89
|
+
|
90
|
+
attributes.flatten!
|
91
|
+
attributes.map! do |attribute|
|
92
|
+
attribute.is_a?(Array) ? attribute : [attribute, :asc]
|
93
|
+
end
|
94
|
+
|
95
|
+
order = attributes.concat(options.to_a).tap do |order|
|
96
|
+
order.flatten! if order.size == 1
|
97
|
+
end
|
98
|
+
|
99
|
+
old_order = @order
|
100
|
+
|
101
|
+
new_order = if option_defined?(:order)
|
102
|
+
old_order = [old_order] unless old_order.first.is_a?(Array)
|
103
|
+
order.first.is_a?(Array) ? old_order.concat(order) : old_order << order
|
104
|
+
else
|
105
|
+
order
|
106
|
+
end
|
107
|
+
|
108
|
+
self.dup.tap { |r| r.set_option(:order, new_order) }
|
109
|
+
end
|
110
|
+
|
111
|
+
def only(*attributes)
|
112
|
+
attributes.flatten!
|
113
|
+
only = if option_defined?(:only)
|
114
|
+
Array.wrap(@only).concat(attributes)
|
115
|
+
else
|
116
|
+
attributes.size == 1 ? attributes.first : attributes
|
117
|
+
end
|
118
|
+
|
119
|
+
self.dup.tap { |r| r.set_option(:only, only) }
|
120
|
+
end
|
121
|
+
|
122
|
+
def distinct
|
123
|
+
self.dup.tap { |r| r.set_option(:distinct, true) }
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_query(method = nil, *args, &block)
|
127
|
+
method ? send("generate_#{method}_query", *args, &block) : find_options
|
128
|
+
end
|
129
|
+
|
130
|
+
def all
|
131
|
+
results = self.select
|
132
|
+
return results if option_defined?(:only) || @superclass.is_a?(String)
|
133
|
+
results.map { |record| @superclass.send(:instantiate, record) }
|
134
|
+
end
|
135
|
+
delegate :first, :last, :to => :all
|
136
|
+
|
137
|
+
def set_option(option, value)
|
138
|
+
instance_variable_set("@#{option}", value)
|
139
|
+
end
|
140
|
+
|
141
|
+
def get_option(option)
|
142
|
+
instance_variable_get("@#{option}")
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def method_missing(method, *args, &block)
|
148
|
+
method = method.to_s
|
149
|
+
if %w(create_index drop_index).include?(method)
|
150
|
+
singleton_class.send(:define_method, method) do
|
151
|
+
raise(ArgumentError, "Missing 'order' condition") unless option_defined?(:order)
|
152
|
+
query(to_query(method, @order)) == 1
|
153
|
+
end.call
|
154
|
+
elsif %w(delete count update insert select checked_write multi_read multi_write list_indexes).include?(method)
|
155
|
+
singleton_class.send(:define_method, method) do |*args, &block|
|
156
|
+
query(to_query(method, *args, &block))
|
157
|
+
end.call(*args, &block)
|
158
|
+
else
|
159
|
+
super
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def generate_list_indexes_query
|
164
|
+
["list-indexes", collection_name]
|
165
|
+
end
|
166
|
+
|
167
|
+
def generate_create_index_query(order)
|
168
|
+
["create-index", collection_name, order]
|
169
|
+
end
|
170
|
+
|
171
|
+
def generate_drop_index_query(order)
|
172
|
+
["drop-index", collection_name, order]
|
173
|
+
end
|
174
|
+
|
175
|
+
def generate_multi_read_query(read_queries = [], &block)
|
176
|
+
yield read_queries if block_given?
|
177
|
+
["multi-read", read_queries]
|
178
|
+
end
|
179
|
+
|
180
|
+
def generate_multi_write_query(queries = [], &block)
|
181
|
+
yield queries if block_given?
|
182
|
+
["multi-write", queries]
|
183
|
+
end
|
184
|
+
|
185
|
+
def generate_checked_write_query(read_query, expected_result, write_query)
|
186
|
+
["checked-write", read_query, expected_result, write_query]
|
187
|
+
end
|
188
|
+
|
189
|
+
def generate_delete_query
|
190
|
+
["delete", collection_name].tap do |q|
|
191
|
+
q << find_options if find_options?
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def generate_update_query(changes)
|
196
|
+
["update", collection_name, changes, find_options]
|
197
|
+
end
|
198
|
+
|
199
|
+
def generate_insert_query(records)
|
200
|
+
["insert", collection_name].tap do |q|
|
201
|
+
q << (records.size == 1 ? records.first : records)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def generate_count_query
|
206
|
+
["count", collection_name].tap do |q|
|
207
|
+
q << find_options if find_options?
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def generate_select_query
|
212
|
+
["select", collection_name].tap do |q|
|
213
|
+
q << find_options if find_options?
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def is_conjunction?(value)
|
218
|
+
%w(and or).include?(value)
|
219
|
+
end
|
220
|
+
|
221
|
+
def option_defined?(option)
|
222
|
+
instance_variable_defined?("@#{option}")
|
223
|
+
end
|
224
|
+
|
225
|
+
def find_options?
|
226
|
+
FIND_OPTIONS_KEYS.any? { |x| option_defined?(x) }
|
227
|
+
end
|
228
|
+
|
229
|
+
def find_options
|
230
|
+
FIND_OPTIONS_KEYS.dup.inject({}) do |h1, x|
|
231
|
+
option_defined?(x) ? h1.tap { |h2| h2[x.to_sym] = instance_variable_get("@#{x}") } : h1
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def collection_name
|
236
|
+
@collection_name ||= @superclass.is_a?(String) ? @superclass : @superclass.collection_name
|
237
|
+
end
|
238
|
+
|
239
|
+
def query(args)
|
240
|
+
Armada.connection.query(args)
|
241
|
+
end
|
242
|
+
|
243
|
+
def build_condition(attribute, value)
|
244
|
+
operator = case value
|
245
|
+
when Array then "in"
|
246
|
+
when Range then value.exclude_end? ? ">=<" : ">=<="
|
247
|
+
else "="
|
248
|
+
end
|
249
|
+
[operator, attribute, build_condition_value(value)]
|
250
|
+
end
|
251
|
+
|
252
|
+
def build_condition_value(value)
|
253
|
+
case value
|
254
|
+
when Range then [value.first, value.last]
|
255
|
+
when Time, DateTime then value.to_i
|
256
|
+
else value
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Armada
|
4
|
+
module Timestamp
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
alias_method_chain :create, :timestamps
|
9
|
+
alias_method_chain :update, :timestamps
|
10
|
+
|
11
|
+
class_attribute :record_timestamps
|
12
|
+
self.record_timestamps = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def touch(attribute = nil)
|
16
|
+
current_time = current_time_from_current_timezone
|
17
|
+
|
18
|
+
if attribute
|
19
|
+
write_attribute(attribute, current_time)
|
20
|
+
else
|
21
|
+
write_attribute('updated_at', current_time) if respond_to?(:updated_at)
|
22
|
+
write_attribute('updated_on', current_time) if respond_to?(:updated_on)
|
23
|
+
end
|
24
|
+
|
25
|
+
save!
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def create_with_timestamps
|
30
|
+
if self.class.record_timestamps?
|
31
|
+
current_time = current_time_from_current_timezone
|
32
|
+
|
33
|
+
write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
|
34
|
+
write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
|
35
|
+
|
36
|
+
write_attribute('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil?
|
37
|
+
write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_on.nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
create_without_timestamps
|
41
|
+
end
|
42
|
+
|
43
|
+
def update_with_timestamps(*args)
|
44
|
+
if self.class.record_timestamps? && changed?
|
45
|
+
current_time = current_time_from_current_timezone
|
46
|
+
|
47
|
+
write_attribute('updated_at', current_time) if respond_to?(:updated_at)
|
48
|
+
write_attribute('updated_on', current_time) if respond_to?(:updated_on)
|
49
|
+
end
|
50
|
+
|
51
|
+
update_without_timestamps(*args)
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_time_from_current_timezone
|
55
|
+
Time.zone.now.to_i
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Armada
|
4
|
+
module Validations
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class UniquenessValidator < ActiveModel::EachValidator
|
8
|
+
|
9
|
+
def validate_each(record, attribute, value)
|
10
|
+
relation = record.class.where(attribute => value)
|
11
|
+
|
12
|
+
Array.wrap(options[:scope]).each do |scope_attribute|
|
13
|
+
relation = relation.where(scope_attribute => record.attributes[scope_attribute])
|
14
|
+
end
|
15
|
+
|
16
|
+
relation = relation.where(:id => {"!=" => record.id}) if record.persisted?
|
17
|
+
|
18
|
+
return if relation.count == 0
|
19
|
+
record.errors.add(attribute, :taken, :default => options[:message], :value => value)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
# Configuration options:
|
27
|
+
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
|
28
|
+
# * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
|
29
|
+
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
|
30
|
+
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
|
31
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
32
|
+
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
|
33
|
+
# method, proc or string should return or evaluate to a true or false value.
|
34
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
35
|
+
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
36
|
+
# method, proc or string should return or evaluate to a true or false value.
|
37
|
+
def validates_uniqueness_of(*attr_names)
|
38
|
+
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/armada.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
require 'yajl'
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
require 'active_model'
|
7
|
+
require 'active_support/core_ext/array/access'
|
8
|
+
require 'active_support/core_ext/array/uniq_by'
|
9
|
+
require 'active_support/core_ext/class/attribute'
|
10
|
+
require 'active_support/core_ext/module/aliasing'
|
11
|
+
require 'active_support/core_ext/module/delegation'
|
12
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
13
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
14
|
+
|
15
|
+
require 'armada/dirty'
|
16
|
+
require 'armada/errors'
|
17
|
+
require 'armada/relation'
|
18
|
+
require 'armada/callbacks'
|
19
|
+
require 'armada/observer'
|
20
|
+
require 'armada/timestamp'
|
21
|
+
require 'armada/connection'
|
22
|
+
require 'armada/validations'
|
23
|
+
require 'armada/finder_methods'
|
24
|
+
require 'armada/database_methods'
|
25
|
+
require 'armada/attribute_methods'
|
26
|
+
|
27
|
+
require 'armada/model'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__),'spec_helper.rb')
|
4
|
+
|
5
|
+
describe Bank, :type => :model do
|
6
|
+
it "has a collection name" do
|
7
|
+
Bank.collection_name.should == "banks"
|
8
|
+
end
|
9
|
+
|
10
|
+
it "will find banks" do
|
11
|
+
Bank.find(@bac.id).should == @bac
|
12
|
+
Bank.find([@bac.id]).should == [@bac]
|
13
|
+
Bank.find(@wfc.id, @c.id).should == [@wfc, @c]
|
14
|
+
Bank.find(@wfc.id, @c.id, @bac.id).should == [@wfc, @c, @bac]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "will raise an error when it does not find a bank" do
|
18
|
+
lambda { Bank.find("481516") }.should raise_error Armada::RecordNotFound
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__),'spec_helper.rb')
|
4
|
+
|
5
|
+
describe "A bank instance" do
|
6
|
+
before do
|
7
|
+
Bank.delete
|
8
|
+
@bank = Bank.new(:name => "Wells Fargo", :rank => 4)
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
Bank.delete
|
13
|
+
end
|
14
|
+
|
15
|
+
it "has read accessors" do
|
16
|
+
@bank.name.should == "Wells Fargo"
|
17
|
+
@bank.attributes[:name].should == "Wells Fargo"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "has write accessors" do
|
21
|
+
@bank.name = "Citigroup"
|
22
|
+
@bank.name.should == "Citigroup"
|
23
|
+
@bank.attributes[:name].should == "Citigroup"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "is comparable to other banks" do
|
27
|
+
@bank.should == Bank.new(:name => "Wells Fargo", :rank => 4)
|
28
|
+
end
|
29
|
+
|
30
|
+
context "that is unsaved" do
|
31
|
+
it "is new" do
|
32
|
+
@bank.new_record?.should be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
it "is not persisted" do
|
36
|
+
@bank.persisted?.should be_false
|
37
|
+
end
|
38
|
+
|
39
|
+
it "can not be destroyed" do
|
40
|
+
@bank.destroy.should be_false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "that is valid" do
|
45
|
+
it "will save" do
|
46
|
+
@bank.save.should be_true
|
47
|
+
end
|
48
|
+
|
49
|
+
it "will save!" do
|
50
|
+
lambda { @bank.save! }.should be_true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "that is invalid" do
|
55
|
+
before do
|
56
|
+
@bank.rank = 5
|
57
|
+
end
|
58
|
+
|
59
|
+
it "will not save" do
|
60
|
+
@bank.save.should be_false
|
61
|
+
end
|
62
|
+
|
63
|
+
it "will not save!" do
|
64
|
+
lambda { @bank.save! }.should raise_error Armada::RecordNotSaved
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "that is saved" do
|
69
|
+
before do
|
70
|
+
@bank.save
|
71
|
+
end
|
72
|
+
|
73
|
+
it "is persisted" do
|
74
|
+
@bank.persisted?.should be_true
|
75
|
+
end
|
76
|
+
|
77
|
+
it "can be destroyed" do
|
78
|
+
@bank.destroy.should be_true
|
79
|
+
@bank.destroyed?.should be_true
|
80
|
+
end
|
81
|
+
|
82
|
+
it "can be changed" do
|
83
|
+
@bank.name = "Citigroup"
|
84
|
+
@bank.rank = 2
|
85
|
+
@bank.save.should be_true
|
86
|
+
end
|
87
|
+
|
88
|
+
it "will retain previous value after a failed update" do
|
89
|
+
@bank.rank = 5
|
90
|
+
@bank.save.should be_false
|
91
|
+
@bank.rank.should == 4
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__),'spec_helper.rb')
|
4
|
+
|
5
|
+
describe Armada::Relation, :type => :model do
|
6
|
+
|
7
|
+
it "will find all banks" do
|
8
|
+
Bank.all.should =~ [@bac, @c, @wfc]
|
9
|
+
end
|
10
|
+
|
11
|
+
it "will implement select correctly" do
|
12
|
+
Bank.select.should =~ [@bac, @c, @wfc].map { |x| x.attributes }
|
13
|
+
Bank.where(:rank => 4).select.should == [@wfc.attributes]
|
14
|
+
end
|
15
|
+
|
16
|
+
it "will implement delete correctly" do
|
17
|
+
Bank.where(:rank => 4).delete.should == 1
|
18
|
+
Bank.delete.should == 2
|
19
|
+
end
|
20
|
+
|
21
|
+
it "will implement count correctly" do
|
22
|
+
Bank.count.should == 3
|
23
|
+
Bank.where(:rank => 4).count.should == 1
|
24
|
+
end
|
25
|
+
|
26
|
+
it "will implement where correctly" do
|
27
|
+
Bank.where(:rank => 4).all.should == [@wfc]
|
28
|
+
Bank.where(:rank => 1..2).all.should =~ [@bac, @c]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "will implement order correctly" do
|
32
|
+
Bank.order(:rank).all.should == [@bac, @c, @wfc]
|
33
|
+
Bank.order(:rank => :asc).all.should == [@bac, @c, @wfc]
|
34
|
+
Bank.order(:rank => :desc).all.should == [@wfc, @c, @bac]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "will implement limit correctly" do
|
38
|
+
Bank.order(:rank => :desc).limit(1).all.should == [@wfc]
|
39
|
+
end
|
40
|
+
|
41
|
+
it "will implement offset correctly" do
|
42
|
+
Bank.order(:rank => :desc).offset(1).all.should == [@c, @bac]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "will implement only correctly" do
|
46
|
+
Bank.order(:rank => :desc).only(:rank).all.should == [4,2,1]
|
47
|
+
Bank.order(:rank => :desc).only(:rank, :name).all.should == [[4,"Wells Fargo"],[2,"Citigroup"],[1,"Bank of America"]]
|
48
|
+
end
|
49
|
+
|
50
|
+
it "will implement to_query correctly for #where" do
|
51
|
+
Bank.where(:rank => 4).to_query.should == {where:["=", :rank, 4]}
|
52
|
+
|
53
|
+
Bank.where(:rank => 4, :name => "Wells Fargo").to_query.should == {where:["and",["=", :rank, 4],["=", :name, "Wells Fargo"]]}
|
54
|
+
Bank.where(:rank => 4).where(:name => "Wells Fargo").to_query.should == {where:["and",["=", :rank, 4],["=", :name, "Wells Fargo"]]}
|
55
|
+
|
56
|
+
Bank.where(:rank => 4).where(:name => "Wells Fargo").where(:id => 1).to_query.should == {where:["and",["=", :rank, 4],["=", :name, "Wells Fargo"],["=", :id, 1]]}
|
57
|
+
Bank.where(:rank => 4, :name => "Wells Fargo").where(:id => 1).to_query.should == {where:["and",["=", :rank, 4],["=", :name, "Wells Fargo"],["=", :id, 1]]}
|
58
|
+
Bank.where(:rank => 4).where(:name => "Wells Fargo", :id => 1).to_query.should == {where:["and",["=", :rank, 4],["=", :name, "Wells Fargo"],["=", :id, 1]]}
|
59
|
+
|
60
|
+
Bank.where(:rank => 4, :name => "Wells Fargo").where(:id => 1, :created_at => {">" => 1}).to_query.should == {where:["and",["=", :rank, 4],["=", :name, "Wells Fargo"],["=", :id, 1],[">", :created_at, 1]]}
|
61
|
+
end
|
62
|
+
|
63
|
+
it "will implement to_query correctly for #where with \"or\" boolean matching" do
|
64
|
+
Bank.where(:name => "Wells Fargo").or.where(:rank => 1).to_query.should == {where:["or",["=", :name, "Wells Fargo"],["=", :rank, 1]]}
|
65
|
+
Bank.where(:name => "Citigroup").or.where(:rank => 1).or.where(:rank => 4).to_query.should == {where:["or", ["=", :name, "Citigroup"], ["=", :rank, 1], ["=", :rank, 4]]}
|
66
|
+
|
67
|
+
Bank.where(:rank => 1).or.where(:rank => 4, :name => "Wells Fargo").to_query.should == {where:["or",["=", :rank, 1], ["and", ["=", :rank, 4], ["=", :name, "Wells Fargo"]]]}
|
68
|
+
Bank.where(:rank => 1).or.where(:rank => 4).where(:name => "Wells Fargo").to_query.should == {where:["or",["=", :rank, 1], ["and", ["=", :rank, 4], ["=", :name, "Wells Fargo"]]]}
|
69
|
+
|
70
|
+
Bank.where(:name => "Wells Fargo", :rank => 4).or.where(:rank => 1).to_query.should == {where:["or",["and", ["=", :name, "Wells Fargo"], ["=", :rank, 4]],["=", :rank, 1]]}
|
71
|
+
|
72
|
+
Bank.where(:name => "Wells Fargo", :rank => 4).or.where(:name => "Bank of America", :rank => 1).to_query.should == {where:["or",["and", ["=", :name, "Wells Fargo"], ["=", :rank, 4]],["and", ["=", :name, "Bank of America"], ["=", :rank, 1]]]}
|
73
|
+
Bank.where(:name => "Wells Fargo", :rank => 4).or.where(:name => "Bank of America").where(:rank => 1).to_query.should == {where:["or",["and", ["=", :name, "Wells Fargo"], ["=", :rank, 4]],["and", ["=", :name, "Bank of America"], ["=", :rank, 1]]]}
|
74
|
+
|
75
|
+
Bank.where(:name => "Wells Fargo", :rank => 4).or.where(:name => "Bank of America").where(:rank => {"=" => 1, "!=" => 4}).to_query.should == {where:["or",["and", ["=", :name, "Wells Fargo"], ["=", :rank, 4]],["and", ["=", :name, "Bank of America"], ["=", :rank, 1], ["!=", :rank, 4]]]}
|
76
|
+
Bank.where(:name => "Wells Fargo", :rank => 4).or.where(:name => "Bank of America", :rank => 1).where(:rank => {"!=" => 4}).to_query.should == {where:["or",["and", ["=", :name, "Wells Fargo"], ["=", :rank, 4]],["and", ["=", :name, "Bank of America"], ["=", :rank, 1], ["!=", :rank, 4]]]}
|
77
|
+
Bank.where(:name => "Wells Fargo").where(:rank => 4).or.where(:name => "Bank of America").where(:rank => {"=" => 1, "!=" => 4}).to_query.should == {where:["or",["and", ["=", :name, "Wells Fargo"], ["=", :rank, 4]],["and", ["=", :name, "Bank of America"], ["=", :rank, 1], ["!=", :rank, 4]]]}
|
78
|
+
end
|
79
|
+
|
80
|
+
it "will implement to_query correctly for #order" do
|
81
|
+
Bank.order(:rank).to_query.should == {order:[:rank, :asc]}
|
82
|
+
|
83
|
+
Bank.order(:rank, :name => :desc).to_query.should == {order:[[:rank, :asc], [:name, :desc]]}
|
84
|
+
Bank.order(:rank).order(:name => :desc).to_query.should == {order:[[:rank, :asc], [:name, :desc]]}
|
85
|
+
|
86
|
+
Bank.order(:rank, :id, :name => :desc).to_query.should == {order:[[:rank, :asc], [:id, :asc], [:name, :desc]]}
|
87
|
+
Bank.order(:rank, :id).order(:name => :desc).to_query.should == {order:[[:rank, :asc], [:id, :asc], [:name, :desc]]}
|
88
|
+
Bank.order(:rank).order(:id, :name => :desc).to_query.should == {order:[[:rank, :asc], [:id, :asc], [:name, :desc]]}
|
89
|
+
|
90
|
+
Bank.order(:rank, :id).order(:name, :created_at).to_query.should == {order:[[:rank, :asc], [:id, :asc], [:name, :asc], [:created_at, :asc]]}
|
91
|
+
end
|
92
|
+
|
93
|
+
it "will implement to_query correctly for #limit" do
|
94
|
+
Bank.limit(1).to_query.should == {limit: 1}
|
95
|
+
end
|
96
|
+
|
97
|
+
it "will implement to_query correctly for #offset" do
|
98
|
+
Bank.offset(1).to_query.should == {offset: 1}
|
99
|
+
end
|
100
|
+
|
101
|
+
it "will implement to_query correctly for #only" do
|
102
|
+
Bank.only(:rank).to_query.should == {only: :rank}
|
103
|
+
|
104
|
+
Bank.only(:rank, :name).to_query.should == {only: [:rank, :name]}
|
105
|
+
Bank.only(:rank).only(:name).to_query.should == {only: [:rank, :name]}
|
106
|
+
|
107
|
+
Bank.only(:id).only(:rank, :name).to_query.should == {only: [:id, :rank, :name]}
|
108
|
+
Bank.only(:id, :rank).only(:name).to_query.should == {only: [:id, :rank, :name]}
|
109
|
+
|
110
|
+
Bank.only(:id, :rank).only(:name, :created_at).to_query.should == {only: [:id, :rank, :name, :created_at]}
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__),'spec_helper.rb')
|
4
|
+
|
5
|
+
describe Armada::Validations, :type => :model do
|
6
|
+
it "should validate uniqueness of rank" do
|
7
|
+
@wfc.rank = 2
|
8
|
+
@wfc.save.should be_false
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should not allow new records to intervene" do
|
12
|
+
@jpm = Bank.new(:name => "JPMorgan Chase", :rank => 2, :price => 42.59, :public => true)
|
13
|
+
@jpm.save.should be_false
|
14
|
+
end
|
15
|
+
end
|
data/test/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
4
|
+
|
5
|
+
require "pp"
|
6
|
+
require "spec"
|
7
|
+
require "armada"
|
8
|
+
|
9
|
+
Time.zone = "UTC"
|
10
|
+
|
11
|
+
Armada.setup!
|
12
|
+
|
13
|
+
class Bank < Armada::Model
|
14
|
+
add_columns :name, :created_at, :updated_at, :rank, :price, :public
|
15
|
+
validates :rank, :inclusion => {:in => 1..4}, :uniqueness => true
|
16
|
+
end
|
17
|
+
|
18
|
+
Spec::Runner.configure do |config|
|
19
|
+
config.before(:each, :type => :model) do
|
20
|
+
Bank.delete
|
21
|
+
|
22
|
+
@bac = Bank.new(:name => "Bank of America", :rank => 1, :price => 16.24, :public => true)
|
23
|
+
@c = Bank.new(:name => "Citigroup", :rank => 2, :price => 3.56, :public => true)
|
24
|
+
@wfc = Bank.new(:name => "Wells Fargo", :rank => 4, :price => 28.89, :public => true)
|
25
|
+
|
26
|
+
[@bac, @c, @wfc].each { |x| x.save }
|
27
|
+
end
|
28
|
+
config.after(:each, :type => :model) do
|
29
|
+
Bank.delete
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: armada
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Sam Aarons
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-04-25 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: yajl-ruby
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 7
|
30
|
+
- 4
|
31
|
+
version: 0.7.4
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: activemodel
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 3
|
43
|
+
- 0
|
44
|
+
- 0
|
45
|
+
- beta3
|
46
|
+
version: 3.0.0.beta3
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rspec
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 1
|
58
|
+
- 3
|
59
|
+
- 0
|
60
|
+
version: 1.3.0
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
description: Armada makes it simple and easy to combine ActiveModel and FleetDB together
|
64
|
+
email:
|
65
|
+
- samaarons@gmail.com
|
66
|
+
executables: []
|
67
|
+
|
68
|
+
extensions: []
|
69
|
+
|
70
|
+
extra_rdoc_files: []
|
71
|
+
|
72
|
+
files:
|
73
|
+
- lib/armada/attribute_methods.rb
|
74
|
+
- lib/armada/callbacks.rb
|
75
|
+
- lib/armada/connection.rb
|
76
|
+
- lib/armada/database_methods.rb
|
77
|
+
- lib/armada/dirty.rb
|
78
|
+
- lib/armada/errors.rb
|
79
|
+
- lib/armada/finder_methods.rb
|
80
|
+
- lib/armada/model.rb
|
81
|
+
- lib/armada/observer.rb
|
82
|
+
- lib/armada/relation.rb
|
83
|
+
- lib/armada/timestamp.rb
|
84
|
+
- lib/armada/validations.rb
|
85
|
+
- lib/armada.rb
|
86
|
+
- test/armada_finder_methods_spec.rb
|
87
|
+
- test/armada_model_spec.rb
|
88
|
+
- test/armada_relation_spec.rb
|
89
|
+
- test/armada_validations_spec.rb
|
90
|
+
- test/spec_helper.rb
|
91
|
+
- LICENSE
|
92
|
+
- Rakefile
|
93
|
+
- README.md
|
94
|
+
has_rdoc: true
|
95
|
+
homepage: http://github.com/saarons/armada
|
96
|
+
licenses: []
|
97
|
+
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
segments:
|
108
|
+
- 1
|
109
|
+
- 9
|
110
|
+
- 1
|
111
|
+
version: 1.9.1
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
segments:
|
117
|
+
- 1
|
118
|
+
- 3
|
119
|
+
- 6
|
120
|
+
version: 1.3.6
|
121
|
+
requirements: []
|
122
|
+
|
123
|
+
rubyforge_project: armada
|
124
|
+
rubygems_version: 1.3.6
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: An ActiveModel interface to FleetDB
|
128
|
+
test_files: []
|
129
|
+
|