rom-sql 0.3.2 → 0.4.0.beta1
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.
- 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
|