active_repository 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +37 -0
- data/Rakefile +2 -0
- data/active_repository.gemspec +23 -0
- data/lib/active_repository/associations.rb +102 -0
- data/lib/active_repository/base.rb +266 -0
- data/lib/active_repository/sql_query_executor.rb +123 -0
- data/lib/active_repository/uniqueness.rb +196 -0
- data/lib/active_repository/version.rb +3 -0
- data/lib/active_repository/write_support.rb +76 -0
- data/lib/active_repository.rb +15 -0
- data/spec/active_repository/base_spec.rb +123 -0
- data/spec/active_repository/sql_query_executor_spec.rb +84 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/shared_examples.rb +582 -0
- data/spec/support/sql_query_shared_examples.rb +317 -0
- metadata +164 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Validations
|
3
|
+
class UniquenessValidator < ActiveModel::EachValidator #:nodoc:
|
4
|
+
def initialize(options)
|
5
|
+
super(options.reverse_merge(:case_sensitive => true))
|
6
|
+
end
|
7
|
+
|
8
|
+
# Unfortunately, we have to tie Uniqueness validators to a class.
|
9
|
+
def setup(klass)
|
10
|
+
@klass = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate_each(record, attribute, value)
|
14
|
+
finder_class = record.class.get_model_class
|
15
|
+
|
16
|
+
finder_class.all.each do |object|
|
17
|
+
if object.id != record.id && object.send(attribute) == record.send(attribute)
|
18
|
+
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope, :conditions).merge(:value => value))
|
19
|
+
break
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
# The check for an existing value should be run from a class that
|
27
|
+
# isn't abstract. This means working down from the current class
|
28
|
+
# (self), to the first non-abstract class. Since classes don't know
|
29
|
+
# their subclasses, we have to build the hierarchy between self and
|
30
|
+
# the record's class.
|
31
|
+
def find_finder_class_for(record) #:nodoc:
|
32
|
+
class_hierarchy = [record.class]
|
33
|
+
|
34
|
+
while class_hierarchy.first != @klass
|
35
|
+
class_hierarchy.prepend(class_hierarchy.first.superclass)
|
36
|
+
end
|
37
|
+
|
38
|
+
class_hierarchy.detect { |klass| klass.respond_to?(:abstract_class?) ? !klass.abstract_class? : true }
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_relation(klass, table, attribute, value) #:nodoc:
|
42
|
+
reflection = klass.reflect_on_association(attribute)
|
43
|
+
if reflection
|
44
|
+
column = klass.columns_hash[reflection.foreign_key]
|
45
|
+
attribute = reflection.foreign_key
|
46
|
+
value = value.attributes[reflection.primary_key_column.name]
|
47
|
+
else
|
48
|
+
column = klass.columns_hash[attribute.to_s]
|
49
|
+
end
|
50
|
+
value = column.limit ? value.to_s[0, column.limit] : value.to_s if !value.nil? && column.text?
|
51
|
+
|
52
|
+
if !options[:case_sensitive] && value && column.text?
|
53
|
+
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
|
54
|
+
relation = klass.connection.case_insensitive_comparison(table, attribute, column, value)
|
55
|
+
else
|
56
|
+
value = klass.connection.case_sensitive_modifier(value) unless value.nil?
|
57
|
+
relation = table[attribute].eq(value)
|
58
|
+
end
|
59
|
+
|
60
|
+
relation
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module ClassMethods
|
65
|
+
# Validates whether the value of the specified attributes are unique
|
66
|
+
# across the system. Useful for making sure that only one user
|
67
|
+
# can be named "davidhh".
|
68
|
+
#
|
69
|
+
# class Person < ActiveRecord::Base
|
70
|
+
# validates_uniqueness_of :user_name
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# It can also validate whether the value of the specified attributes are
|
74
|
+
# unique based on a <tt>:scope</tt> parameter:
|
75
|
+
#
|
76
|
+
# class Person < ActiveRecord::Base
|
77
|
+
# validates_uniqueness_of :user_name, scope: :account_id
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# Or even multiple scope parameters. For example, making sure that a
|
81
|
+
# teacher can only be on the schedule once per semester for a particular
|
82
|
+
# class.
|
83
|
+
#
|
84
|
+
# class TeacherSchedule < ActiveRecord::Base
|
85
|
+
# validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# It is also possible to limit the uniqueness constraint to a set of
|
89
|
+
# records matching certain conditions. In this example archived articles
|
90
|
+
# are not being taken into consideration when validating uniqueness
|
91
|
+
# of the title attribute:
|
92
|
+
#
|
93
|
+
# class Article < ActiveRecord::Base
|
94
|
+
# validates_uniqueness_of :title, conditions: where('status != ?', 'archived')
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# When the record is created, a check is performed to make sure that no
|
98
|
+
# record exists in the database with the given value for the specified
|
99
|
+
# attribute (that maps to a column). When the record is updated,
|
100
|
+
# the same check is made but disregarding the record itself.
|
101
|
+
#
|
102
|
+
# Configuration options:
|
103
|
+
#
|
104
|
+
# * <tt>:message</tt> - Specifies a custom error message (default is:
|
105
|
+
# "has already been taken").
|
106
|
+
# * <tt>:scope</tt> - One or more columns by which to limit the scope of
|
107
|
+
# the uniqueness constraint.
|
108
|
+
# * <tt>:conditions</tt> - Specify the conditions to be included as a
|
109
|
+
# <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
|
110
|
+
# (e.g. <tt>conditions: where('status = ?', 'active')</tt>).
|
111
|
+
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
|
112
|
+
# non-text columns (+true+ by default).
|
113
|
+
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
|
114
|
+
# attribute is +nil+ (default is +false+).
|
115
|
+
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
|
116
|
+
# attribute is blank (default is +false+).
|
117
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
118
|
+
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
119
|
+
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
|
120
|
+
# proc or string should return or evaluate to a +true+ or +false+ value.
|
121
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
|
122
|
+
# determine if the validation should ot occur (e.g. <tt>unless: :skip_validation</tt>,
|
123
|
+
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
124
|
+
# method, proc or string should return or evaluate to a +true+ or +false+
|
125
|
+
# value.
|
126
|
+
#
|
127
|
+
# === Concurrency and integrity
|
128
|
+
#
|
129
|
+
# Using this validation method in conjunction with ActiveRecord::Base#save
|
130
|
+
# does not guarantee the absence of duplicate record insertions, because
|
131
|
+
# uniqueness checks on the application level are inherently prone to race
|
132
|
+
# conditions. For example, suppose that two users try to post a Comment at
|
133
|
+
# the same time, and a Comment's title must be unique. At the database-level,
|
134
|
+
# the actions performed by these users could be interleaved in the following manner:
|
135
|
+
#
|
136
|
+
# User 1 | User 2
|
137
|
+
# ------------------------------------+--------------------------------------
|
138
|
+
# # User 1 checks whether there's |
|
139
|
+
# # already a comment with the title |
|
140
|
+
# # 'My Post'. This is not the case. |
|
141
|
+
# SELECT * FROM comments |
|
142
|
+
# WHERE title = 'My Post' |
|
143
|
+
# |
|
144
|
+
# | # User 2 does the same thing and also
|
145
|
+
# | # infers that his title is unique.
|
146
|
+
# | SELECT * FROM comments
|
147
|
+
# | WHERE title = 'My Post'
|
148
|
+
# |
|
149
|
+
# # User 1 inserts his comment. |
|
150
|
+
# INSERT INTO comments |
|
151
|
+
# (title, content) VALUES |
|
152
|
+
# ('My Post', 'hi!') |
|
153
|
+
# |
|
154
|
+
# | # User 2 does the same thing.
|
155
|
+
# | INSERT INTO comments
|
156
|
+
# | (title, content) VALUES
|
157
|
+
# | ('My Post', 'hello!')
|
158
|
+
# |
|
159
|
+
# | # ^^^^^^
|
160
|
+
# | # Boom! We now have a duplicate
|
161
|
+
# | # title!
|
162
|
+
#
|
163
|
+
# This could even happen if you use transactions with the 'serializable'
|
164
|
+
# isolation level. The best way to work around this problem is to add a unique
|
165
|
+
# index to the database table using
|
166
|
+
# ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
|
167
|
+
# rare case that a race condition occurs, the database will guarantee
|
168
|
+
# the field's uniqueness.
|
169
|
+
#
|
170
|
+
# When the database catches such a duplicate insertion,
|
171
|
+
# ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
|
172
|
+
# exception. You can either choose to let this error propagate (which
|
173
|
+
# will result in the default Rails exception page being shown), or you
|
174
|
+
# can catch it and restart the transaction (e.g. by telling the user
|
175
|
+
# that the title already exists, and asking him to re-enter the title).
|
176
|
+
# This technique is also known as optimistic concurrency control:
|
177
|
+
# http://en.wikipedia.org/wiki/Optimistic_concurrency_control
|
178
|
+
#
|
179
|
+
# The bundled ActiveRecord::ConnectionAdapters distinguish unique index
|
180
|
+
# constraint errors from other types of database errors by throwing an
|
181
|
+
# ActiveRecord::RecordNotUnique exception. For other adapters you will
|
182
|
+
# have to parse the (database-specific) exception message to detect such
|
183
|
+
# a case.
|
184
|
+
#
|
185
|
+
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
|
186
|
+
#
|
187
|
+
# * ActiveRecord::ConnectionAdapters::MysqlAdapter
|
188
|
+
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter
|
189
|
+
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter
|
190
|
+
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
191
|
+
def validates_uniqueness_of(*attr_names)
|
192
|
+
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'active_hash'
|
2
|
+
require 'active_repository/sql_query_executor'
|
3
|
+
|
4
|
+
begin
|
5
|
+
klass = Module.const_get(ActiveRecord::Rollback)
|
6
|
+
unless klass.is_a?(Class)
|
7
|
+
raise "Not defined"
|
8
|
+
end
|
9
|
+
rescue
|
10
|
+
module ActiveRecord
|
11
|
+
class ActiveRecordError < StandardError
|
12
|
+
end
|
13
|
+
class Rollback < ActiveRecord::ActiveRecordError
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ActiveHash
|
19
|
+
class Base
|
20
|
+
def self.insert(record)
|
21
|
+
if self.all.map(&:to_s).include?(record.to_s)
|
22
|
+
record_index.delete(record.id.to_s)
|
23
|
+
self.all.delete(record)
|
24
|
+
end
|
25
|
+
|
26
|
+
if record_index[record.id.to_s].nil? || !self.all.map(&:to_s).include?(record.to_s)
|
27
|
+
@records ||= []
|
28
|
+
record.attributes[:id] ||= next_id
|
29
|
+
|
30
|
+
validate_unique_id(record) if dirty
|
31
|
+
mark_dirty
|
32
|
+
|
33
|
+
if record.valid?
|
34
|
+
add_to_record_index({ record.id.to_s => @records.length })
|
35
|
+
@records << record
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.where(query)
|
41
|
+
if query.is_a?(String)
|
42
|
+
return ActiveHash::SQLQueryExecutor.execute(self, query)
|
43
|
+
else
|
44
|
+
(@records || []).select do |record|
|
45
|
+
query.all? { |col, match| record[col] == match }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.validate_unique_id(record)
|
51
|
+
raise IdError.new("Duplicate Id found for record #{record.attributes}") if record_index.has_key?(record.id.to_s)
|
52
|
+
end
|
53
|
+
|
54
|
+
def readonly?
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def save(*args)
|
59
|
+
record = self.class.find_by_id(self.id)
|
60
|
+
|
61
|
+
self.class.insert(self) if record.nil? && record != self
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
def persisted?
|
66
|
+
other = self.class.find_by_id(id)
|
67
|
+
other.present? && other.created_at
|
68
|
+
end
|
69
|
+
|
70
|
+
def eql?(other)
|
71
|
+
(other.instance_of?(self.class) || other.instance_of?(self.class.get_model_class)) && id.present? && (id == other.id) && (created_at == other.created_at)
|
72
|
+
end
|
73
|
+
|
74
|
+
alias == eql?
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_repository'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'active_model'
|
5
|
+
require 'active_model/naming'
|
6
|
+
rescue LoadError
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'active_hash'
|
11
|
+
require 'associations/associations'
|
12
|
+
rescue LoadError
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'active_repository/base'
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/shared_examples'
|
3
|
+
|
4
|
+
require 'active_repository'
|
5
|
+
require "active_record"
|
6
|
+
require "mongoid"
|
7
|
+
|
8
|
+
describe ActiveRepository, "Base" do
|
9
|
+
|
10
|
+
before do
|
11
|
+
class Country < ActiveRepository::Base
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
after do
|
16
|
+
Object.send :remove_const, :Country
|
17
|
+
end
|
18
|
+
|
19
|
+
context "in_memory" do
|
20
|
+
before do
|
21
|
+
Country.fields :name, :monarch, :language
|
22
|
+
Country.set_model_class(Country)
|
23
|
+
Country.set_save_in_memory(true)
|
24
|
+
|
25
|
+
Country.create(:id => 1, :name => "US", :language => 'English')
|
26
|
+
Country.create(:id => 2, :name => "Canada", :language => 'English', :monarch => "The Crown of England")
|
27
|
+
Country.create(:id => 3, :name => "Mexico", :language => 'Spanish')
|
28
|
+
Country.create(:id => 4, :name => "UK", :language => 'English', :monarch => "The Crown of England")
|
29
|
+
Country.create(:id => 5, :name => "Brazil")
|
30
|
+
end
|
31
|
+
|
32
|
+
it_behaves_like '.update_attributes'
|
33
|
+
it_behaves_like '.all'
|
34
|
+
it_behaves_like '.where'
|
35
|
+
it_behaves_like '.exists?'
|
36
|
+
it_behaves_like '.count'
|
37
|
+
it_behaves_like '.first'
|
38
|
+
it_behaves_like '.last'
|
39
|
+
it_behaves_like '.find'
|
40
|
+
it_behaves_like '.find_by_id'
|
41
|
+
it_behaves_like 'custom finders'
|
42
|
+
it_behaves_like '#method_missing'
|
43
|
+
it_behaves_like '#attributes'
|
44
|
+
it_behaves_like 'reader_methods'
|
45
|
+
it_behaves_like 'interrogator methods'
|
46
|
+
it_behaves_like '#id'
|
47
|
+
it_behaves_like '#quoted_id'
|
48
|
+
it_behaves_like '#to_param'
|
49
|
+
it_behaves_like '#persisted?'
|
50
|
+
it_behaves_like '#eql?'
|
51
|
+
it_behaves_like '#=='
|
52
|
+
it_behaves_like '#hash'
|
53
|
+
it_behaves_like '#readonly?'
|
54
|
+
it_behaves_like '#cache_key'
|
55
|
+
it_behaves_like '#save'
|
56
|
+
it_behaves_like '.create'
|
57
|
+
it_behaves_like '#valid?'
|
58
|
+
it_behaves_like '#new_record?'
|
59
|
+
it_behaves_like '.transaction'
|
60
|
+
it_behaves_like '.delete_all'
|
61
|
+
end
|
62
|
+
|
63
|
+
context "active_record" do
|
64
|
+
before do
|
65
|
+
Country.fields :name, :monarch, :language
|
66
|
+
|
67
|
+
class CountryModel < ActiveRecord::Base
|
68
|
+
self.table_name = 'countries'
|
69
|
+
establish_connection :adapter => "sqlite3", :database => ":memory:"
|
70
|
+
connection.create_table(:countries, :force => true) do |t|
|
71
|
+
t.string :name
|
72
|
+
t.string :monarch
|
73
|
+
t.string :language
|
74
|
+
t.datetime :created_at
|
75
|
+
t.datetime :updated_at
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
Country.set_model_class(CountryModel)
|
80
|
+
Country.set_save_in_memory(false)
|
81
|
+
|
82
|
+
Country.create(:id => 1, :name => "US", :language => 'English')
|
83
|
+
Country.create(:id => 2, :name => "Canada", :language => 'English', :monarch => "The Crown of England")
|
84
|
+
Country.create(:id => 3, :name => "Mexico", :language => 'Spanish')
|
85
|
+
Country.create(:id => 4, :name => "UK", :language => 'English', :monarch => "The Crown of England")
|
86
|
+
Country.create(:id => 5, :name => "Brazil")
|
87
|
+
end
|
88
|
+
|
89
|
+
after do
|
90
|
+
Object.send :remove_const, :CountryModel
|
91
|
+
end
|
92
|
+
|
93
|
+
it_behaves_like '.update_attributes'
|
94
|
+
it_behaves_like '.all'
|
95
|
+
it_behaves_like '.where'
|
96
|
+
it_behaves_like '.exists?'
|
97
|
+
it_behaves_like '.count'
|
98
|
+
it_behaves_like '.first'
|
99
|
+
it_behaves_like '.last'
|
100
|
+
it_behaves_like '.find'
|
101
|
+
it_behaves_like '.find_by_id'
|
102
|
+
it_behaves_like 'custom finders'
|
103
|
+
it_behaves_like '#method_missing'
|
104
|
+
it_behaves_like '#attributes'
|
105
|
+
it_behaves_like 'reader_methods'
|
106
|
+
it_behaves_like 'interrogator methods'
|
107
|
+
it_behaves_like '#id'
|
108
|
+
it_behaves_like '#quoted_id'
|
109
|
+
it_behaves_like '#to_param'
|
110
|
+
it_behaves_like '#persisted?'
|
111
|
+
it_behaves_like '#eql?'
|
112
|
+
it_behaves_like '#=='
|
113
|
+
it_behaves_like '#hash'
|
114
|
+
it_behaves_like '#readonly?'
|
115
|
+
it_behaves_like '#cache_key'
|
116
|
+
it_behaves_like '#save'
|
117
|
+
it_behaves_like '.create'
|
118
|
+
it_behaves_like '#valid?'
|
119
|
+
it_behaves_like '#new_record?'
|
120
|
+
it_behaves_like '.transaction'
|
121
|
+
it_behaves_like '.delete_all'
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/sql_query_shared_examples'
|
3
|
+
|
4
|
+
require 'active_repository'
|
5
|
+
require "active_record"
|
6
|
+
require "mongoid"
|
7
|
+
|
8
|
+
describe ActiveRepository, "Base" do
|
9
|
+
|
10
|
+
before do
|
11
|
+
class Country < ActiveRepository::Base
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
after do
|
16
|
+
Object.send :remove_const, :Country
|
17
|
+
end
|
18
|
+
|
19
|
+
context "in_memory" do
|
20
|
+
before do
|
21
|
+
Country.fields :name, :monarch, :language, :founded_at
|
22
|
+
Country.set_model_class(Country)
|
23
|
+
Country.set_save_in_memory(true)
|
24
|
+
|
25
|
+
Country.create(:id => 1, :name => "US", :language => 'English')
|
26
|
+
Country.create(:id => 2, :name => "Canada", :language => 'English', :monarch => "The Crown of England")
|
27
|
+
Country.create(:id => 3, :name => "Mexico", :language => 'Spanish')
|
28
|
+
Country.create(:id => 4, :name => "UK", :language => 'English', :monarch => "The Crown of England")
|
29
|
+
Country.create(:id => 5, :name => "Brazil", :founded_at => Time.parse('1500-04-22 13:34:25'))
|
30
|
+
end
|
31
|
+
|
32
|
+
describe ".where" do
|
33
|
+
it_behaves_like '='
|
34
|
+
it_behaves_like '>'
|
35
|
+
it_behaves_like '>='
|
36
|
+
it_behaves_like '<'
|
37
|
+
it_behaves_like '<='
|
38
|
+
it_behaves_like 'between'
|
39
|
+
it_behaves_like 'is'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "active_record" do
|
44
|
+
before do
|
45
|
+
Country.fields :name, :monarch, :language, :founded_at
|
46
|
+
|
47
|
+
class CountryModel < ActiveRecord::Base
|
48
|
+
self.table_name = 'countries'
|
49
|
+
establish_connection :adapter => "sqlite3", :database => ":memory:"
|
50
|
+
connection.create_table(:countries, :force => true) do |t|
|
51
|
+
t.string :name
|
52
|
+
t.string :monarch
|
53
|
+
t.string :language
|
54
|
+
t.datetime :founded_at
|
55
|
+
t.datetime :created_at
|
56
|
+
t.datetime :updated_at
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
Country.set_model_class(CountryModel)
|
61
|
+
Country.set_save_in_memory(false)
|
62
|
+
|
63
|
+
Country.create(:id => 1, :name => "US", :language => 'English')
|
64
|
+
Country.create(:id => 2, :name => "Canada", :language => 'English', :monarch => "The Crown of England")
|
65
|
+
Country.create(:id => 3, :name => "Mexico", :language => 'Spanish')
|
66
|
+
Country.create(:id => 4, :name => "UK", :language => 'English', :monarch => "The Crown of England")
|
67
|
+
Country.create(:id => 5, :name => "Brazil", :founded_at => Time.parse('1500-04-22 13:34:25'))
|
68
|
+
end
|
69
|
+
|
70
|
+
after do
|
71
|
+
Object.send :remove_const, :CountryModel
|
72
|
+
end
|
73
|
+
|
74
|
+
describe ".where" do
|
75
|
+
it_behaves_like '='
|
76
|
+
it_behaves_like '>'
|
77
|
+
it_behaves_like '>='
|
78
|
+
it_behaves_like '<'
|
79
|
+
it_behaves_like '<='
|
80
|
+
it_behaves_like 'between'
|
81
|
+
it_behaves_like 'is'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|