rom-sql 0.3.2 → 0.4.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +55 -18
- data/.rubocop_todo.yml +15 -0
- data/.travis.yml +10 -5
- data/CHANGELOG.md +18 -0
- data/Gemfile +8 -1
- data/Guardfile +24 -0
- data/README.md +14 -22
- data/Rakefile +13 -5
- data/lib/rom/sql.rb +5 -5
- data/lib/rom/sql/commands.rb +7 -49
- data/lib/rom/sql/commands/create.rb +29 -0
- data/lib/rom/sql/commands/delete.rb +18 -0
- data/lib/rom/sql/commands/transaction.rb +17 -0
- data/lib/rom/sql/commands/update.rb +54 -0
- data/lib/rom/sql/commands_ext/postgres.rb +24 -0
- data/lib/rom/sql/header.rb +8 -9
- data/lib/rom/sql/migration.rb +26 -0
- data/lib/rom/sql/plugin/pagination.rb +93 -0
- data/lib/rom/sql/rake_task.rb +2 -0
- data/lib/rom/sql/relation.rb +320 -0
- data/lib/rom/sql/relation/associations.rb +104 -0
- data/lib/rom/sql/relation/class_methods.rb +47 -0
- data/lib/rom/sql/relation/inspection.rb +16 -0
- data/lib/rom/sql/repository.rb +59 -0
- data/lib/rom/sql/support/rails_log_subscriber.rb +1 -1
- data/lib/rom/sql/tasks/migration_tasks.rake +56 -0
- data/lib/rom/sql/version.rb +1 -1
- data/rom-sql.gemspec +2 -3
- data/spec/integration/commands/create_spec.rb +66 -8
- data/spec/integration/commands/delete_spec.rb +22 -3
- data/spec/integration/commands/update_spec.rb +57 -6
- data/spec/integration/read_spec.rb +42 -1
- data/spec/shared/database_setup.rb +10 -5
- data/spec/spec_helper.rb +17 -0
- data/spec/support/active_support_notifications_spec.rb +5 -4
- data/spec/support/rails_log_subscriber_spec.rb +2 -2
- data/spec/unit/logger_spec.rb +5 -3
- data/spec/unit/many_to_many_spec.rb +2 -2
- data/spec/unit/migration_spec.rb +34 -0
- data/spec/unit/migration_tasks_spec.rb +99 -0
- data/spec/unit/one_to_many_spec.rb +0 -2
- data/spec/unit/plugin/pagination_spec.rb +73 -0
- data/spec/unit/relation_spec.rb +49 -3
- data/spec/unit/repository_spec.rb +33 -0
- data/spec/unit/schema_spec.rb +5 -17
- metadata +32 -35
- data/lib/rom/sql/adapter.rb +0 -100
- data/lib/rom/sql/relation_inclusion.rb +0 -149
- data/lib/rom/sql/support/sequel_dataset_ext.rb +0 -33
- data/spec/unit/adapter_spec.rb +0 -48
- data/spec/unit/config_spec.rb +0 -54
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rom/sql/commands'
|
2
|
+
require 'rom/sql/commands/transaction'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
module SQL
|
6
|
+
module Commands
|
7
|
+
class Delete < ROM::Commands::Delete
|
8
|
+
include Transaction
|
9
|
+
|
10
|
+
def execute
|
11
|
+
deleted = target.to_a
|
12
|
+
target.delete
|
13
|
+
deleted
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rom/commands/result'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module SQL
|
5
|
+
module Commands
|
6
|
+
module Transaction
|
7
|
+
ROM::SQL::Rollback = Class.new(Sequel::Rollback)
|
8
|
+
|
9
|
+
def transaction(options = {}, &block)
|
10
|
+
ROM::Commands::Result::Success.new(
|
11
|
+
relation.dataset.db.transaction(options, &block)
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rom/sql/commands'
|
2
|
+
require 'rom/sql/commands/transaction'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
module SQL
|
6
|
+
module Commands
|
7
|
+
class Update < ROM::Commands::Update
|
8
|
+
include Transaction
|
9
|
+
|
10
|
+
option :original, type: Hash, reader: true
|
11
|
+
|
12
|
+
alias_method :to, :call
|
13
|
+
|
14
|
+
def execute(tuple)
|
15
|
+
attributes = input[tuple]
|
16
|
+
validator.call(attributes)
|
17
|
+
|
18
|
+
changed = diff(attributes.to_h)
|
19
|
+
|
20
|
+
if changed.any?
|
21
|
+
update(changed)
|
22
|
+
else
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def change(original)
|
28
|
+
self.class.new(relation, options.merge(original: original))
|
29
|
+
end
|
30
|
+
|
31
|
+
def update(tuple)
|
32
|
+
pks = relation.map { |t| t[primary_key] }
|
33
|
+
dataset = relation.dataset
|
34
|
+
dataset.update(tuple)
|
35
|
+
dataset.unfiltered.where(primary_key => pks).to_a
|
36
|
+
end
|
37
|
+
|
38
|
+
def primary_key
|
39
|
+
relation.primary_key
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def diff(tuple)
|
45
|
+
if original
|
46
|
+
Hash[tuple.to_a - (tuple.to_a & original.to_a)]
|
47
|
+
else
|
48
|
+
tuple
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rom/sql/commands/create'
|
2
|
+
require 'rom/sql/commands/update'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
module SQL
|
6
|
+
module Commands
|
7
|
+
module Postgres
|
8
|
+
module Create
|
9
|
+
def insert(tuples)
|
10
|
+
tuples.map do |tuple|
|
11
|
+
relation.dataset.returning(*relation.columns).insert(tuple)
|
12
|
+
end.flatten
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Update
|
17
|
+
def update(tuple)
|
18
|
+
relation.dataset.returning(*relation.columns).update(tuple)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/rom/sql/header.rb
CHANGED
@@ -2,13 +2,12 @@ module ROM
|
|
2
2
|
module SQL
|
3
3
|
# @private
|
4
4
|
class Header
|
5
|
-
include Charlatan.new(:columns)
|
6
5
|
include Equalizer.new(:columns, :table)
|
7
6
|
|
8
|
-
attr_reader :table
|
7
|
+
attr_reader :columns, :table
|
9
8
|
|
10
9
|
def initialize(columns, table)
|
11
|
-
|
10
|
+
@columns = columns
|
12
11
|
@table = table
|
13
12
|
end
|
14
13
|
|
@@ -25,19 +24,19 @@ module ROM
|
|
25
24
|
end
|
26
25
|
|
27
26
|
def names
|
28
|
-
map { |col| :"#{col.to_s.split('___').last}" }
|
27
|
+
columns.map { |col| :"#{col.to_s.split('___').last}" }
|
29
28
|
end
|
30
29
|
|
31
30
|
def project(*names)
|
32
|
-
find_all { |col| names.include?(col) }
|
31
|
+
self.class.new(columns.find_all { |col| names.include?(col) }, table)
|
33
32
|
end
|
34
33
|
|
35
34
|
def qualified
|
36
|
-
map { |col| :"#{table}__#{col}" }
|
35
|
+
self.class.new(columns.map { |col| :"#{table}__#{col}" }, table)
|
37
36
|
end
|
38
37
|
|
39
38
|
def rename(options)
|
40
|
-
map
|
39
|
+
self.class.new(columns.map { |col|
|
41
40
|
new_name = options[col]
|
42
41
|
|
43
42
|
if new_name
|
@@ -45,11 +44,11 @@ module ROM
|
|
45
44
|
else
|
46
45
|
col
|
47
46
|
end
|
48
|
-
|
47
|
+
}, table)
|
49
48
|
end
|
50
49
|
|
51
50
|
def prefix(col_prefix)
|
52
|
-
rename(Hash[map { |col| [col, :"#{col_prefix}_#{col}"] }])
|
51
|
+
rename(Hash[columns.map { |col| [col, :"#{col_prefix}_#{col}"] }])
|
53
52
|
end
|
54
53
|
end
|
55
54
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ROM
|
2
|
+
module SQL
|
3
|
+
class Migration
|
4
|
+
::Sequel.extension :migration
|
5
|
+
|
6
|
+
DEFAULT_PATH = 'db/migrate'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_writer :path
|
10
|
+
attr_accessor :connection
|
11
|
+
|
12
|
+
def path
|
13
|
+
@path || DEFAULT_PATH
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(options = {})
|
17
|
+
::Sequel::Migrator.run(connection, path, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def create(&block)
|
21
|
+
::Sequel.migration(&block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module ROM
|
2
|
+
module SQL
|
3
|
+
module Plugin
|
4
|
+
module Pagination
|
5
|
+
class Pager
|
6
|
+
include Options
|
7
|
+
include Equalizer.new(:current_page, :per_page)
|
8
|
+
|
9
|
+
option :current_page, reader: true, default: 1
|
10
|
+
option :per_page, reader: true
|
11
|
+
|
12
|
+
attr_reader :dataset
|
13
|
+
attr_reader :current_page
|
14
|
+
|
15
|
+
def initialize(dataset, options = {})
|
16
|
+
@dataset = dataset
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def next_page
|
21
|
+
num = current_page + 1
|
22
|
+
num if total_pages >= num
|
23
|
+
end
|
24
|
+
|
25
|
+
def prev_page
|
26
|
+
num = current_page - 1
|
27
|
+
num if num > 0
|
28
|
+
end
|
29
|
+
|
30
|
+
def total
|
31
|
+
dataset.unlimited.count
|
32
|
+
end
|
33
|
+
|
34
|
+
def total_pages
|
35
|
+
(total / per_page) + 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def at(num, per_page = options[:per_page])
|
39
|
+
self.class.new(
|
40
|
+
dataset.offset((num-1)*per_page).limit(per_page),
|
41
|
+
options.merge(current_page: num, per_page: per_page)
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
alias_method :limit_value, :per_page
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.included(klass)
|
49
|
+
super
|
50
|
+
|
51
|
+
klass.class_eval do
|
52
|
+
defines :per_page
|
53
|
+
|
54
|
+
option :pager, reader: true, default: proc { |relation|
|
55
|
+
Pager.new(relation.dataset, per_page: relation.class.per_page)
|
56
|
+
}
|
57
|
+
|
58
|
+
exposed_relations.merge([:pager, :page, :per_page])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Paginate a relation
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# rom.relation(:users).class.per_page(10)
|
66
|
+
# rom.relation(:users).page(1)
|
67
|
+
# rom.relation(:users).pager # => info about pagination
|
68
|
+
#
|
69
|
+
# @return [Relation]
|
70
|
+
#
|
71
|
+
# @api public
|
72
|
+
def page(num)
|
73
|
+
num = num.to_i
|
74
|
+
next_pager = pager.at(num)
|
75
|
+
__new__(next_pager.dataset, pager: next_pager)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Set limit for pagination
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# rom.relation(:users).page(2).per_page(10)
|
82
|
+
#
|
83
|
+
# @api public
|
84
|
+
def per_page(num)
|
85
|
+
num = num.to_i
|
86
|
+
next_pager = pager.at(pager.current_page, num)
|
87
|
+
__new__(next_pager.dataset, pager: next_pager)
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,320 @@
|
|
1
|
+
require 'rom/sql/header'
|
2
|
+
|
3
|
+
require 'rom/sql/relation/class_methods'
|
4
|
+
require 'rom/sql/relation/inspection'
|
5
|
+
require 'rom/sql/relation/associations'
|
6
|
+
|
7
|
+
module ROM
|
8
|
+
module SQL
|
9
|
+
# Sequel-specific relation extensions
|
10
|
+
#
|
11
|
+
class Relation < ROM::Relation
|
12
|
+
extend ClassMethods
|
13
|
+
|
14
|
+
include Inspection
|
15
|
+
include Associations
|
16
|
+
|
17
|
+
attr_reader :header, :table
|
18
|
+
|
19
|
+
# @api private
|
20
|
+
def initialize(dataset, registry = {})
|
21
|
+
super
|
22
|
+
@table = dataset.opts[:from].first
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return a header for this relation
|
26
|
+
#
|
27
|
+
# @return [Header]
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
def header
|
31
|
+
@header ||= Header.new(dataset.opts[:select] || dataset.columns, table)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Return raw column names
|
35
|
+
#
|
36
|
+
# @return [Array<Symbol>]
|
37
|
+
#
|
38
|
+
# @api private
|
39
|
+
def columns
|
40
|
+
dataset.columns
|
41
|
+
end
|
42
|
+
|
43
|
+
# @api public
|
44
|
+
def project(*names)
|
45
|
+
select(*header.project(*names))
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api public
|
49
|
+
def rename(options)
|
50
|
+
select(*header.rename(options))
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api public
|
54
|
+
def prefix(name = Inflector.singularize(table))
|
55
|
+
rename(header.prefix(name).to_h)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @api public
|
59
|
+
def qualified
|
60
|
+
select(*qualified_columns)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @api public
|
64
|
+
def qualified_columns
|
65
|
+
header.qualified.to_a
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get first tuple from the relation
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# users.first
|
72
|
+
#
|
73
|
+
# @return [Relation]
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def first
|
77
|
+
dataset.first
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get last tuple from the relation
|
81
|
+
#
|
82
|
+
# @example
|
83
|
+
# users.first
|
84
|
+
#
|
85
|
+
# @return [Relation]
|
86
|
+
#
|
87
|
+
# @api public
|
88
|
+
def last
|
89
|
+
dataset.last
|
90
|
+
end
|
91
|
+
|
92
|
+
# Return relation count
|
93
|
+
#
|
94
|
+
# @example
|
95
|
+
# users.count # => 12
|
96
|
+
#
|
97
|
+
# @return [Relation]
|
98
|
+
#
|
99
|
+
# @api public
|
100
|
+
def count
|
101
|
+
dataset.count
|
102
|
+
end
|
103
|
+
|
104
|
+
# Select specific columns for select clause
|
105
|
+
#
|
106
|
+
# @example
|
107
|
+
# users.select(:id, :name)
|
108
|
+
#
|
109
|
+
# @return [Relation]
|
110
|
+
#
|
111
|
+
# @api public
|
112
|
+
def select(*args, &block)
|
113
|
+
__new__(dataset.__send__(__method__, *args, &block))
|
114
|
+
end
|
115
|
+
|
116
|
+
# Append specific columns to select clause
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# users.select(:id, :name).select_append(:email)
|
120
|
+
#
|
121
|
+
# @return [Relation]
|
122
|
+
#
|
123
|
+
# @api public
|
124
|
+
def select_append(*args, &block)
|
125
|
+
__new__(dataset.__send__(__method__, *args, &block))
|
126
|
+
end
|
127
|
+
|
128
|
+
# Restrict a relation to match criteria
|
129
|
+
#
|
130
|
+
# @example
|
131
|
+
# users.where(name: 'Jane')
|
132
|
+
#
|
133
|
+
# @return [Relation]
|
134
|
+
#
|
135
|
+
# @api public
|
136
|
+
def where(*args, &block)
|
137
|
+
__new__(dataset.__send__(__method__, *args, &block))
|
138
|
+
end
|
139
|
+
|
140
|
+
# Set order for the relation
|
141
|
+
#
|
142
|
+
# @example
|
143
|
+
# users.order(:name)
|
144
|
+
#
|
145
|
+
# @return [Relation]
|
146
|
+
#
|
147
|
+
# @api public
|
148
|
+
def order(*args, &block)
|
149
|
+
__new__(dataset.__send__(__method__, *args, &block))
|
150
|
+
end
|
151
|
+
|
152
|
+
# Reverse the order of the relation
|
153
|
+
#
|
154
|
+
# @example
|
155
|
+
# users.order(:name).reverse
|
156
|
+
#
|
157
|
+
# @return [Relation]
|
158
|
+
#
|
159
|
+
# @api public
|
160
|
+
def reverse(*args, &block)
|
161
|
+
__new__(dataset.__send__(__method__, *args, &block))
|
162
|
+
end
|
163
|
+
|
164
|
+
# Limit a relation to a specific number of tuples
|
165
|
+
#
|
166
|
+
# @example
|
167
|
+
# users.limit(1)
|
168
|
+
#
|
169
|
+
# @return [Relation]
|
170
|
+
#
|
171
|
+
# @api public
|
172
|
+
def limit(*args, &block)
|
173
|
+
__new__(dataset.__send__(__method__, *args, &block))
|
174
|
+
end
|
175
|
+
|
176
|
+
# Set offset for the relation
|
177
|
+
#
|
178
|
+
# @example
|
179
|
+
# users.limit(10).offset(2)
|
180
|
+
#
|
181
|
+
# @return [Relation]
|
182
|
+
#
|
183
|
+
# @api public
|
184
|
+
def offset(*args, &block)
|
185
|
+
__new__(dataset.__send__(__method__, *args, &block))
|
186
|
+
end
|
187
|
+
|
188
|
+
# Map tuples from the relation
|
189
|
+
#
|
190
|
+
# @example
|
191
|
+
# users.map { |user| ... }
|
192
|
+
#
|
193
|
+
# @api public
|
194
|
+
def map(&block)
|
195
|
+
to_enum.map(&block)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Join other relation using inner join
|
199
|
+
#
|
200
|
+
# @param [Symbol] relation name
|
201
|
+
# @param [Hash] join keys
|
202
|
+
#
|
203
|
+
# @return [Relation]
|
204
|
+
#
|
205
|
+
# @api public
|
206
|
+
def inner_join(*args, &block)
|
207
|
+
__new__(dataset.__send__(__method__, *args, &block))
|
208
|
+
end
|
209
|
+
|
210
|
+
# Join other relation using left outer join
|
211
|
+
#
|
212
|
+
# @param [Symbol] relation name
|
213
|
+
# @param [Hash] join keys
|
214
|
+
#
|
215
|
+
# @return [Relation]
|
216
|
+
#
|
217
|
+
# @api public
|
218
|
+
def left_join(*args, &block)
|
219
|
+
__new__(dataset.__send__(__method__, *args, &block))
|
220
|
+
end
|
221
|
+
|
222
|
+
# Group by specific columns
|
223
|
+
#
|
224
|
+
# @example
|
225
|
+
# tasks.group(:user_id)
|
226
|
+
#
|
227
|
+
# @return [Relation]
|
228
|
+
#
|
229
|
+
# @api public
|
230
|
+
def group(*args, &block)
|
231
|
+
__new__(dataset.__send__(__method__, *args, &block))
|
232
|
+
end
|
233
|
+
|
234
|
+
# Group by specific columns and count by group
|
235
|
+
#
|
236
|
+
# @example
|
237
|
+
# tasks.group_and_count(:user_id)
|
238
|
+
# # => [{ user_id: 1, count: 2 }, { user_id: 2, count: 3 }]
|
239
|
+
#
|
240
|
+
# @return [Relation]
|
241
|
+
#
|
242
|
+
# @api public
|
243
|
+
def group_and_count(*args, &block)
|
244
|
+
__new__(dataset.__send__(__method__, *args, &block))
|
245
|
+
end
|
246
|
+
|
247
|
+
# Select and group by specific columns
|
248
|
+
#
|
249
|
+
# @example
|
250
|
+
# tasks.select_group(:user_id)
|
251
|
+
# # => [{ user_id: 1 }, { user_id: 2 }]
|
252
|
+
#
|
253
|
+
# @return [Relation]
|
254
|
+
#
|
255
|
+
# @api public
|
256
|
+
def select_group(*args, &block)
|
257
|
+
__new__(dataset.__send__(__method__, *args, &block))
|
258
|
+
end
|
259
|
+
|
260
|
+
# Insert tuple into relation
|
261
|
+
#
|
262
|
+
# @example
|
263
|
+
# users.insert(name: 'Jane')
|
264
|
+
#
|
265
|
+
# @param [Hash] tuple
|
266
|
+
#
|
267
|
+
# @return [Relation]
|
268
|
+
#
|
269
|
+
# @api public
|
270
|
+
def insert(*args, &block)
|
271
|
+
dataset.insert(*args, &block)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Update tuples in the relation
|
275
|
+
#
|
276
|
+
# @example
|
277
|
+
# users.update(name: 'Jane')
|
278
|
+
# users.where(name: 'Jane').update(name: 'Jane Doe')
|
279
|
+
#
|
280
|
+
# @return [Relation]
|
281
|
+
#
|
282
|
+
# @api public
|
283
|
+
def update(*args, &block)
|
284
|
+
dataset.update(*args, &block)
|
285
|
+
end
|
286
|
+
|
287
|
+
# Delete tuples from the relation
|
288
|
+
#
|
289
|
+
# @example
|
290
|
+
# users.delete # deletes all
|
291
|
+
# users.where(name: 'Jane').delete # delete tuples
|
292
|
+
# from restricted relation
|
293
|
+
#
|
294
|
+
# @return [Relation]
|
295
|
+
#
|
296
|
+
# @api public
|
297
|
+
def delete(*args, &block)
|
298
|
+
dataset.delete(*args, &block)
|
299
|
+
end
|
300
|
+
|
301
|
+
# Return if a restricted relation has 0 tuples
|
302
|
+
#
|
303
|
+
# @example
|
304
|
+
# users.unique?(email: 'jane@doe.org') # true
|
305
|
+
#
|
306
|
+
# users.insert(email: 'jane@doe.org')
|
307
|
+
#
|
308
|
+
# users.unique?(email: 'jane@doe.org') # false
|
309
|
+
#
|
310
|
+
# @param [Hash] criteria hash for the where clause
|
311
|
+
#
|
312
|
+
# @return [Relation]
|
313
|
+
#
|
314
|
+
# @api public
|
315
|
+
def unique?(criteria)
|
316
|
+
where(criteria).count.zero?
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|