activerecord-filter 6.0.0.6 → 6.1.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c34aa8bbd31df158c37ad5a80c46c31dff156bfb2a6e0bbb7e699d8919eb9aa1
4
- data.tar.gz: eb154ad06ccc2b7b24c4b7ebea90bd4708b95a92db2b7d8fd92f74e2ca71c4c6
3
+ metadata.gz: 15ee3eb761b9e63875afecd98becf6b5f6b0482937610ee0a9303cfe7d8d0b75
4
+ data.tar.gz: 85b0b14cee45da61b44326810765078ad7a838af806168950a0ee50d48b5a2c6
5
5
  SHA512:
6
- metadata.gz: 8d715a7a707abc56c16512e2aee2fe4fd4995e82466c3651d2caf14d38bb72f0201e0445c72614c4d295d05ccc6adf66666a1287644b33612d14aa472de804b9
7
- data.tar.gz: 2f4535f34a7e762c4ce68abb18530e7f0b9ca9b7f8bd6a16e6389626f806806bc89a486104c17d61cb7361dc106437a203d0fe598ce8ace34136c23048282b66
6
+ metadata.gz: b6f47b5bc745550bb48c1cd6aa85642e20511b5235befe0acd123909e0faa422c2b53662a26e8db4da8ae44029b0d0ddf44fd5179e48f23ee25e3463634bc164
7
+ data.tar.gz: 2240aadf3eea6b58f1b2d16e7e0cb785e7250938538533bf33d565871a15d845f0a1ea496cdec8b520bc575c66f330953dd300eabc1d75be405c029468e19124
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # ActiveRecord::filter
1
+ # ActiveRecord::Filter
2
2
 
3
- `ActiveRecord::filter` provides and easy way to accept user input and filter a query by the input.
3
+ `ActiveRecord::Filter` provides and easy way to accept user input and filter a query by the input.
4
4
 
5
5
  Installtion
6
6
  -----------
7
7
 
8
- - Add `gem 'activerecord-filter', require: 'active_record/filter'
8
+ - Add `gem 'activerecord-filter', require: 'active_record/filter'`
9
9
  - Run `bundle install`
10
10
 
11
11
  Examples
@@ -14,48 +14,92 @@ Examples
14
14
  Normal columns:
15
15
 
16
16
  ```ruby
17
- Property.filter(:id => 5).to_sql
18
- # => "...WHERE properties.id = 5"
17
+ Property.filter(id: 5).to_sql
18
+ Property.filter(id: {eq: 5}).to_sql
19
+ Property.filter(id: {equal_to: 5}).to_sql
20
+ # => "... WHERE properties.id = 5 ..."
19
21
 
20
- Property.filter(:id => [5, 10, 15]).to_sql
21
- # => "...WHERE properties.id IN (5, 10, 15)"
22
+ Property.filter(id: {not: 5}).to_sql
23
+ Property.filter(id: {neq: 5}).to_sql
24
+ Property.filter(id: {not_equal: 5}).to_sql
25
+ # => "... WHERE properties.id != 5 ..."
22
26
 
23
- Property.filter(:id => {:gt => 5}).to_sql
24
- # => "...WHERE properties.id > 5"
27
+ Property.filter(id: [5, 10, 15]).to_sql
28
+ # => "... WHERE properties.id IN (5, 10, 15) ..."
25
29
 
26
- Property.filter(:id => {:gte => 5}).to_sql
27
- # => "...WHERE properties.id >= 5"
30
+ Property.filter(id: {in: [5, 10, 15]}).to_sql
31
+ # => "... WHERE properties.id IN (5, 10, 15) ..."
28
32
 
29
- Property.filter(:id => {:lt => 5}).to_sql
30
- # => "...WHERE properties.id < 5"
33
+ Property.filter(id: {not_in: [5, 10, 15]}).to_sql
34
+ # => "... WHERE properties.id NOT IN (5, 10, 15) ..."
31
35
 
32
- Property.filter(:id => {:lte => 5}).to_sql
33
- # => "...WHERE properties.id <= 5"
36
+ Property.filter(id: {gt: 5}).to_sql
37
+ Property.filter(id: {greater_than: 5}).to_sql
38
+ # => "... WHERE properties.id > 5 ..."
34
39
 
35
- Property.filter(:address_id => nil).to_sql
36
- # => "...WHERE properties.address_id IS NULL..."
40
+ Property.filter(id: {gte: 5}).to_sql
41
+ Property.filter(id: {gteq: 5}).to_sql
42
+ Property.filter(id: {greater_than_or_equal_to: 5}).to_sql
43
+ # => "... WHERE properties.id >= 5 ..."
37
44
 
38
- Property.filter(:address_id => false).to_sql
39
- # => "...WHERE properties.address_id IS NULL..."
45
+ Property.filter(id: {lt: 5}).to_sql
46
+ Property.filter(id: {less_than: 5}).to_sql
47
+ # => "... WHERE properties.id < 5 ..."
40
48
 
41
- Property.filter(:address_id => true).to_sql
42
- # => "...WHERE properties.address_id IS NOT NULL..."
49
+ Property.filter(id: {lte: 5}).to_sql
50
+ Property.filter(id: {lteq: 5}).to_sql
51
+ Property.filter(id: {less_than_or_equal_to: 5}).to_sql
52
+ # => "... WHERE properties.id <= 5 ..."
53
+
54
+ Property.filter(address_id: nil).to_sql
55
+ # => "... WHERE properties.address_id IS NULL ..."
56
+
57
+ Property.filter(address_id: false).to_sql
58
+ # => "... WHERE properties.address_id IS NULL ..."
59
+
60
+ Property.filter(boolean_column: false).to_sql
61
+ # => "... WHERE properties.boolean_column = FALSE ..."
62
+
63
+ Property.filter(address_id: true).to_sql
64
+ # => "... WHERE properties.address_id IS NOT NULL ..."
65
+
66
+ Property.filter(boolean_column: true).to_sql
67
+ # => "... WHERE properties.boolean_column = TRUE ..."
68
+ ```
69
+
70
+ String columns:
71
+
72
+ ```ruby
73
+ Property.filter(name: {like: 'nam%'}).to_sql
74
+ # => "... WHERE properties.name LIKE 'nam%' ..."
75
+
76
+ Property.filter(name: {ilike: 'nam%'}).to_sql
77
+ # => "... WHERE properties.name ILIKE 'nam%' ..."
78
+
79
+ Property.filter(name: {ts_match: 'name'}).to_sql
80
+ # => "... WHERE to_tsvector("properties"."name") @@ to_tsquery('name') ..."
43
81
  ```
44
82
 
45
83
  It can also work with array columns:
46
84
 
47
85
  ```ruby
48
- Property.filter(:tags => 'Skyscraper').to_sql
86
+ Property.filter(tags: 'Skyscraper').to_sql
49
87
  # => "...WHERE properties.tags = '{'Skyscraper'}'..."
50
88
 
51
- Property.filter(:tags => ['Skyscraper', 'Brick']).to_sql
52
- # => "...WHERE (properties.tags = '{"Skyscraper", "Brick"}')..."
89
+ Property.filter(tags: ['Skyscraper', 'Brick']).to_sql
90
+ # => "...WHERE properties.tags = '{"Skyscraper", "Brick"}'..."
91
+
92
+ Property.filter(tags: {overlaps: ['Skyscraper', 'Brick']}).to_sql
93
+ # => "...WHERE properties.tags && '{"Skyscraper", "Brick"}'..."
53
94
 
54
- Property.filter(:tags => {overlaps: ['Skyscraper', 'Brick']}).to_sql
55
- # => "...WHERE properties.tags && '{"Skyscraper", "Brick"}')..."
95
+ Property.filter(tags: {contains: ['Skyscraper', 'Brick']}).to_sql
96
+ # => "...WHERE accounts.tags @> '{"Skyscraper", "Brick"}'..."
56
97
 
57
- Property.filter(:tags => {contains: ['Skyscraper', 'Brick']}).to_sql
58
- # => "...WHERE accounts.tags @> '{"Skyscraper", "Brick"}')..."
98
+ Property.filter(tags: {excludes: ['Skyscraper', 'Brick']}).to_sql
99
+ # => "...WHERE NOT (accounts.tags @> '{"Skyscraper", "Brick"}')..."
100
+
101
+ Property.filter(tags: {contained_by: ['Skyscraper', 'Brick']}).to_sql
102
+ # => "...WHERE accounts.tags <@ '{"Skyscraper", "Brick"}'..."
59
103
  ```
60
104
 
61
105
  And JSON columns:
@@ -70,6 +114,12 @@ Property.filter(metadata: { contains: { key: 'value' } }).to_sql
70
114
  Property.filter(metadata: { has_key: 'key' }).to_sql
71
115
  # => "...WHERE "properties"."metadata" ? 'key'..."
72
116
 
117
+ Property.filter(metadata: { has_keys: ['key1', 'key2'] }).to_sql
118
+ # => "...WHERE "properties"."metadata" ?& array['key1', 'key2']..."
119
+
120
+ Property.filter(metadata: { has_any_key: ['key1', 'key2'] }).to_sql
121
+ # => "...WHERE "properties"."metadata" ?| array['key1', 'key2']..."
122
+
73
123
  Property.filter("metadata.key": { eq: 'value' }).to_sql
74
124
  # => "...WHERE "properties"."metadata" #> '{key}' = 'value'..."
75
125
  ```
@@ -77,7 +127,7 @@ Property.filter("metadata.key": { eq: 'value' }).to_sql
77
127
  It can also sort on relations:
78
128
 
79
129
  ```ruby
80
- Photo.filter(:property => {name: 'Empire State'}).to_sql
81
- # => "...INNER JOIN properties ON properties.id = photos.property_id
82
- # => " WHERE properties.name = 'Empire State'"
130
+ Photo.filter(property: {name: 'Empire State'}).to_sql
131
+ # => "... LEFT OUTER JOIN properties ON properties.id = photos.property_id ...
132
+ # => "... WHERE properties.name = 'Empire State'"
83
133
  ```
@@ -4,6 +4,20 @@ require 'arel/extensions'
4
4
  class ActiveRecord::UnkownFilterError < NoMethodError
5
5
  end
6
6
 
7
+ class ActiveRecord::Associations::AliasTracker
8
+
9
+ def initialize(connection, aliases)
10
+ @aliases = aliases
11
+ @connection = connection
12
+ @relation_trail = {}
13
+ end
14
+
15
+ def aliased_table_for_relation(trail, arel_table, &block)
16
+ @relation_trail[trail] ||= aliased_table_for(arel_table, &block)
17
+ end
18
+
19
+ end
20
+
7
21
  module ActiveRecord::Filter
8
22
 
9
23
  delegate :filter, :filter_for, to: :all
@@ -60,16 +74,21 @@ module ActiveRecord
60
74
  elsif reflection = klass._reflections[key.to_s]
61
75
  if value.is_a?(Hash)
62
76
  relations << if reflection.polymorphic?
63
- join_klass = value[:as].safe_constantize
64
-
65
- right_table = join_klass.arel_table.alias("#{join_klass.table_name}_as_#{reflection.name}")
77
+ value = value.dup
78
+ join_klass = value.delete(:as).safe_constantize
79
+ right_table = join_klass.arel_table
66
80
  left_table = reflection.active_record.arel_table
67
81
 
68
82
  on = right_table[join_klass.primary_key].
69
83
  eq(left_table[reflection.foreign_key]).
70
84
  and(left_table[reflection.foreign_type].eq(join_klass.name))
71
85
 
72
- left_table.join(right_table, Arel::Nodes::OuterJoin).on(on).join_sources
86
+ cross_boundry_joins = join_klass.left_outer_joins(ActiveRecord::PredicateBuilder.filter_joins(join_klass, value).flatten).send(:build_joins, [])
87
+
88
+ [
89
+ left_table.join(right_table, Arel::Nodes::OuterJoin).on(on).join_sources,
90
+ cross_boundry_joins
91
+ ]
73
92
  else
74
93
  {
75
94
  key => build_filter_joins(reflection.klass, value, [], custom)
@@ -84,7 +103,7 @@ module ActiveRecord
84
103
  elsif value != true && value != false && value != 'true' && value != 'false' && !value.nil?
85
104
  relations << key
86
105
  end
87
- elsif !klass.columns_hash.has_key?(key.to_s) && key.to_s.ends_with?('_ids') && reflection = klass._reflections[key.to_s.gsub(/_ids$/, 's')]
106
+ elsif !klass.columns_hash.has_key?(key.to_s) && key.to_s.end_with?('_ids') && reflection = klass._reflections[key.to_s.gsub(/_ids$/, 's')]
88
107
  relations << reflection.name
89
108
  elsif reflection = klass.reflect_on_all_associations(:has_and_belongs_to_many).find {|r| r.join_table == key.to_s && value.keys.first.to_s == r.association_foreign_key.to_s }
90
109
  reflection = klass._reflections[klass._reflections[reflection.name.to_s].send(:delegate_reflection).options[:through].to_s]
@@ -142,7 +161,7 @@ module ActiveRecord
142
161
  expand_filter_for_column(key, column, value, relation_trail)
143
162
  elsif relation = klass.reflect_on_association(key)
144
163
  expand_filter_for_relationship(relation, value, relation_trail, alias_tracker)
145
- elsif key.to_s.ends_with?('_ids') && relation = klass.reflect_on_association(key.to_s.gsub(/_ids$/, 's'))
164
+ elsif key.to_s.end_with?('_ids') && relation = klass.reflect_on_association(key.to_s.gsub(/_ids$/, 's'))
146
165
  expand_filter_for_relationship(relation, {id: value}, relation_trail, alias_tracker)
147
166
  elsif relation = klass.reflect_on_all_associations(:has_and_belongs_to_many).find {|r| r.join_table == key.to_s && value.keys.first.to_s == r.association_foreign_key.to_s }
148
167
  expand_filter_for_join_table(relation, value, relation_trail, alias_tracker)
@@ -160,7 +179,7 @@ module ActiveRecord
160
179
  end
161
180
 
162
181
  def expand_filter_for_column(key, column, value, relation_trail)
163
- attribute = table.arel_attribute(column.name)
182
+ attribute = table.arel_table[column.name]
164
183
  relation_trail.each do |rt|
165
184
  attribute = Arel::Attributes::Relation.new(attribute, rt)
166
185
  end
@@ -196,13 +215,13 @@ module ActiveRecord
196
215
  def expand_filter_for_arel_attribute(column, attribute, key, value)
197
216
  case key.to_sym
198
217
  when :contains
199
- attribute.contains(column.array ? Array(value) : value)
218
+ attribute.contains(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
200
219
  when :contained_by
201
- attribute.contained_by(column.array ? Array(value) : value)
220
+ attribute.contained_by(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
202
221
  when :equal_to, :eq
203
222
  attribute.eq(value)
204
223
  when :excludes
205
- attribute.excludes(Array(value))
224
+ attribute.excludes(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
206
225
  when :greater_than, :gt
207
226
  attribute.gt(value)
208
227
  when :greater_than_or_equal_to, :gteq, :gte
@@ -238,14 +257,16 @@ module ActiveRecord
238
257
  attribute.lt(value)
239
258
  when :less_than_or_equal_to, :lteq, :lte
240
259
  attribute.lteq(value)
241
- when :like, :ilike
242
- attribute.matches(value)
260
+ when :like
261
+ attribute.matches(value, nil, true)
262
+ when :ilike
263
+ attribute.matches(value, nil, false)
243
264
  when :not, :not_equal, :neq
244
265
  attribute.not_eq(value)
245
266
  when :not_in
246
267
  attribute.not_in(value)
247
268
  when :overlaps
248
- attribute.overlaps(value)
269
+ attribute.overlaps(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
249
270
  when :not_overlaps
250
271
  attribute.not_overlaps(value)
251
272
  when :ts_match
@@ -277,14 +298,14 @@ module ActiveRecord
277
298
  if value == true || value == 'true'
278
299
  counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
279
300
  if relation.active_record.column_names.include?(counter_cache_column_name.to_s)
280
- return table.arel_attribute(counter_cache_column_name.to_sym).gt(0)
301
+ return table.arel_table[counter_cache_column_name.to_sym].gt(0)
281
302
  else
282
303
  raise "Not Supported: #{relation.name}"
283
304
  end
284
305
  elsif value == false || value == 'false'
285
306
  counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
286
307
  if relation.active_record.column_names.include?(counter_cache_column_name.to_s)
287
- return table.arel_attribute(counter_cache_column_name.to_sym).eq(0)
308
+ return table.arel_table[counter_cache_column_name.to_sym].eq(0)
288
309
  else
289
310
  raise "Not Supported: #{relation.name}"
290
311
  end
@@ -292,33 +313,31 @@ module ActiveRecord
292
313
 
293
314
  when :belongs_to
294
315
  if value == true || value == 'true'
295
- return table.arel_attribute(relation.foreign_key).not_eq(nil)
316
+ return table.arel_table[relation.foreign_key].not_eq(nil)
296
317
  elsif value == false || value == 'false' || value.nil?
297
- return table.arel_attribute(relation.foreign_key).eq(nil)
318
+ return table.arel_table[relation.foreign_key].eq(nil)
298
319
  end
299
320
  end
300
321
 
301
- builder = if relation.polymorphic?
322
+ if relation.polymorphic?
302
323
  value = value.dup
303
324
  klass = value.delete(:as).safe_constantize
304
325
 
305
- self.class.new(TableMetadata.new(
306
- klass,
307
- Arel::Table.new("#{klass.table_name}_as_#{relation.name}", type_caster: klass.type_caster),
326
+ builder = self.class.new(TableMetadata.new(
327
+ klass,
328
+ alias_tracker.aliased_table_for_relation(relation_trail + ["#{klass.table_name}_as_#{relation.name}"], klass.arel_table) { klass.arel_table.name },
308
329
  relation
309
330
  ))
331
+ builder.build_from_filter_hash(value, relation_trail + ["#{klass.table_name}_as_#{relation.name}"], alias_tracker)
310
332
  else
311
- self.class.new(TableMetadata.new(
333
+ builder = self.class.new(TableMetadata.new(
312
334
  relation.klass,
313
- alias_tracker.aliased_table_for(
314
- relation.table_name,
315
- relation.alias_candidate(table.send(:arel_table).name),
316
- relation.klass.type_caster
317
- ),
335
+ alias_tracker.aliased_table_for_relation(relation_trail + [relation.name], relation.klass.arel_table) { relation.alias_candidate(table.arel_table.name || relation.klass.arel_table) },
318
336
  relation
319
337
  ))
338
+ builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
320
339
  end
321
- builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
340
+
322
341
  end
323
342
 
324
343
 
@@ -326,11 +345,7 @@ module ActiveRecord
326
345
  relation = relation.active_record._reflections[relation.active_record._reflections[relation.name.to_s].send(:delegate_reflection).options[:through].to_s]
327
346
  builder = self.class.new(TableMetadata.new(
328
347
  relation.klass,
329
- alias_tracker.aliased_table_for(
330
- relation.table_name,
331
- relation.alias_candidate(table.send(:arel_table).name),
332
- relation.klass.type_caster
333
- ),
348
+ alias_tracker.aliased_table_for_relation(relation_trail + [relation.name], relation.klass.arel_table) { relation.alias_candidate(table.arel_table.name || relation.klass.arel_table) },
334
349
  relation
335
350
  ))
336
351
  builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
@@ -424,7 +439,7 @@ class ActiveRecord::Relation
424
439
  arel
425
440
  end
426
441
 
427
- def build_filters(manager, aliases)
442
+ def build_filters(manager, alias_tracker)
428
443
  @filters.each do |filters|
429
444
  manager.where(filter_clause_factory.build(filters, alias_tracker).ast)
430
445
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Filter
3
- VERSION = '6.0.0.6'
3
+ VERSION = '6.1.0.1'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-filter
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0.6
4
+ version: 6.1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Bracy
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-27 00:00:00.000000000 Z
11
+ date: 2021-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 6.0.0
19
+ version: 6.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 6.0.0
26
+ version: 6.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: arel-extensions
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 6.0.0.5
33
+ version: 6.1.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 6.0.0.5
40
+ version: 6.1.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pg
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 6.0.0
61
+ version: 6.1.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 6.0.0
68
+ version: 6.1.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -142,14 +142,14 @@ dependencies:
142
142
  requirements:
143
143
  - - ">="
144
144
  - !ruby/object:Gem::Version
145
- version: 6.0.0
145
+ version: 6.1.0
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
- version: 6.0.0
152
+ version: 6.1.0
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: faker
155
155
  requirement: !ruby/object:Gem::Requirement
@@ -194,7 +194,7 @@ homepage: https://github.com/malomalo/activerecord-filter
194
194
  licenses:
195
195
  - MIT
196
196
  metadata: {}
197
- post_install_message:
197
+ post_install_message:
198
198
  rdoc_options:
199
199
  - "--main"
200
200
  - README.md
@@ -211,8 +211,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
211
  - !ruby/object:Gem::Version
212
212
  version: '0'
213
213
  requirements: []
214
- rubygems_version: 3.0.6
215
- signing_key:
214
+ rubygems_version: 3.2.3
215
+ signing_key:
216
216
  specification_version: 4
217
217
  summary: A safe way to accept user parameters and query against your ActiveRecord
218
218
  Models