armada 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 +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
|
+
|