Class: ModelSet

Inherits:
Object
  • Object
show all
Includes:
ActiveSupport::CoreExtensions::Array::Conversions, Enumerable
Defined in:
lib/model_set.rb,
lib/model_set/query.rb,
lib/model_set/raw_query.rb,
lib/model_set/set_query.rb,
lib/model_set/sql_query.rb,
lib/model_set/conditions.rb,
lib/model_set/solr_query.rb,
lib/model_set/conditioned.rb,
lib/model_set/sphinx_query.rb,
lib/model_set/raw_sql_query.rb,
lib/model_set/sql_base_query.rb

Defined Under Namespace

Modules: Conditioned Classes: Conditions, Query, RawQuery, RawSQLQuery, SQLBaseQuery, SQLQuery, SetQuery, SolrQuery, SphinxQuery

Constant Summary collapse

MAX_CACHE_SIZE =
1000
QUERY_TYPES =
{
  :set    => SetQuery,
  :sql    => SQLQuery,
  :solr   => SolrQuery,
  :sphinx => SphinxQuery,
  :raw    => RawQuery,
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(query_or_models) ⇒ ModelSet

Returns a new instance of ModelSet.



28
29
30
31
32
33
34
35
36
37
38
# File 'lib/model_set.rb', line 28

def initialize(query_or_models)
  if query_or_models.kind_of?(Query)
    @query = query_or_models
  elsif query_or_models.kind_of?(self.class)
    self.ids = query_or_models.ids
    @models_by_id = query_or_models.models_by_id
  elsif query_or_models
    self.ids = as_ids(query_or_models)
  end
  @created_at = Time.now
end

Instance Attribute Details

#created_atObject (readonly)

Returns the value of attribute created_at.



26
27
28
# File 'lib/model_set.rb', line 26

def created_at
  @created_at
end

#queryObject

Returns the value of attribute query.



347
348
349
# File 'lib/model_set.rb', line 347

def query
  @query
end

Class Method Details

.allObject



503
504
505
# File 'lib/model_set.rb', line 503

def self.all
  new(nil)
end

.as_id(model) ⇒ Object



487
488
489
490
491
492
493
494
495
496
497
# File 'lib/model_set.rb', line 487

def self.as_id(model)
  id = model_class.as_id(model) if model_class.respond_to?(:as_id)
  return id if id

  case model
  when model_class        then model.send(id_field)
  when ActiveRecord::Base then model.id
  when String             then model.split('-').last.to_i
  else                         model.to_i
  end
end

.as_ids(models) ⇒ Object



477
478
479
480
481
482
483
484
485
# File 'lib/model_set.rb', line 477

def self.as_ids(models)
  return [] unless models
  if models.kind_of?(self)
    models.ids
  else
    models = [models] if not models.kind_of?(Enumerable)
    models.collect {|model| as_id(model)}
  end
end

.as_set(models) ⇒ Object



473
474
475
# File 'lib/model_set.rb', line 473

def self.as_set(models)
  models.kind_of?(self) ? models : new(models)
end

.constructor(filter_name, opts = nil) ⇒ Object



523
524
525
526
527
528
529
530
531
532
# File 'lib/model_set.rb', line 523

def self.constructor(filter_name, opts = nil)
  (class << self; self; end).module_eval do
    define_method filter_name do |*args|
      if opts
        args.last.kind_of?(Hash) ? args.last.reverse_merge!(opts.clone) : args << opts.clone
      end
      self.all.send("#{filter_name}!", *args)
    end
  end
end

.emptyObject



499
500
501
# File 'lib/model_set.rb', line 499

def self.empty
  new([])
end

.find(opts) ⇒ Object



507
508
509
510
511
512
513
514
515
# File 'lib/model_set.rb', line 507

def self.find(opts)
  set = all
  set.add_joins!(opts[:joins])            if opts[:joins]
  set.add_conditions!(opts[:conditions])  if opts[:conditions]
  set.order_by!(opts[:order])             if opts[:order]
  set.limit!(opts[:limit], opts[:offset]) if opts[:limit]
  set.page!(opts[:page])                  if opts[:page]
  set
end

.find_by_sql(sql) ⇒ Object



517
518
519
520
521
# File 'lib/model_set.rb', line 517

def self.find_by_sql(sql)
  query = RawSQLQuery.new(self)
  query.sql = sql
  new(query)
end

.id_field(id_field = nil) ⇒ Object



570
571
572
573
574
575
576
# File 'lib/model_set.rb', line 570

def self.id_field(id_field = nil)
  if id_field.nil?
    @id_field ||= :id
  else
    @id_field = id_field
  end
end

.id_field_with_prefixObject



586
587
588
# File 'lib/model_set.rb', line 586

def self.id_field_with_prefix
  self.id_field.is_a?(String) ? self.id_field : "#{self.table_name}.#{self.id_field}"
end

.id_type(id_type = nil) ⇒ Object



578
579
580
581
582
583
584
# File 'lib/model_set.rb', line 578

def self.id_type(id_type = nil)
  if id_type.nil?
    @id_type ||= :integer
  else
    @id_type = id_type.to_sym
  end
end

.model_class(model_class = nil) ⇒ Object

By default the model class is the set class without the trailing “Set”. If you use a different model class you can call “model_class MyModel” in your set class.



536
537
538
539
540
541
542
543
544
# File 'lib/model_set.rb', line 536

def self.model_class(model_class = nil)
  return ActiveRecord::Base if self == ModelSet

  if model_class.nil?
    @model_class ||= self.name.sub(/#{set_class_suffix}$/,'').constantize
  else
    @model_class = model_class
  end
end

.model_nameObject



554
555
556
# File 'lib/model_set.rb', line 554

def self.model_name
  model_class.name
end

.query_model_class(query_model_class = nil) ⇒ Object



546
547
548
549
550
551
552
# File 'lib/model_set.rb', line 546

def self.query_model_class(query_model_class = nil)
  if query_model_class.nil?
    @query_model_class ||= model_class
  else
    @query_model_class = query_model_class
  end
end

.set_class_suffixObject



558
559
560
# File 'lib/model_set.rb', line 558

def self.set_class_suffix
  'Set'
end

.table_name(table_name = nil) ⇒ Object



562
563
564
565
566
567
568
# File 'lib/model_set.rb', line 562

def self.table_name(table_name = nil)
  if table_name.nil?
    @table_name ||= model_class.table_name
  else
    @table_name = table_name
  end
end

Instance Method Details

#[](*args) ⇒ Object Also known as: slice

FIXME make work for nested offsets



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/model_set.rb', line 91

def [](*args)
  case args.size
  when 1
    index = args[0]
    if index.kind_of?(Range)
      offset = index.begin
      limit  = index.end - index.begin
      limit += 1 unless index.exclude_end?
      self.limit(limit, offset)
    else
      by_id(ids[index])
    end
  when 2
    offset, limit = args
    self.limit(limit, offset)
  else
    raise ArgumentError.new("wrong number of arguments (#{args.size} for 1 or 2)")
  end
end

#add_fields!(fields) ⇒ Object



420
421
422
423
424
425
426
# File 'lib/model_set.rb', line 420

def add_fields!(fields)
  raise 'cannot use both add_fields and include_models' if @included_models
  ( @add_fields ||= {} ).merge!(fields)

  # We have to reload the models because we are adding additional fields.
  self.clear_cache!
end

#aggregate(*args) ⇒ Object



438
439
440
441
# File 'lib/model_set.rb', line 438

def aggregate(*args)
  anchor!(:sql)
  query.aggregate(*args)
end

#anchor!(type = default_query_type, *args) ⇒ Object



357
358
359
360
361
362
363
364
# File 'lib/model_set.rb', line 357

def anchor!(type = default_query_type, *args)
  return unless type
  query_class = query_class(type)
  if not query_type?(query_class)
    self.query = query_class.new(self, *args)
  end
  self
end

#any?Boolean

Returns:

  • (Boolean)


297
298
299
300
301
# File 'lib/model_set.rb', line 297

def any?
  return super if block_given?
  return false if query.nil?
  size > 0
end

#by_id(id) ⇒ Object



84
85
86
87
88
# File 'lib/model_set.rb', line 84

def by_id(id)
  return nil if id.nil?
  fetch_models([id]) unless models_by_id[id]
  models_by_id[id] || nil
end

#clear_cache!Object



443
444
445
446
# File 'lib/model_set.rb', line 443

def clear_cache!
  @models_by_id = nil
  self
end

#clone_fieldsObject



466
467
468
469
470
471
# File 'lib/model_set.rb', line 466

def clone_fields
  # Do a deep copy of the fields we want to modify.
  @query             = @query.clone             if @query
  @add_fields        = @add_fields.clone        if @add_fields
  @included_models   = @included_models.clone   if @included_models
end

#countObject



288
289
290
# File 'lib/model_set.rb', line 288

def count
  query.count
end

#current_pageObject

for will_paginate



307
308
309
# File 'lib/model_set.rb', line 307

def current_page # for will_paginate
  query.page
end

#default_query_typeObject



373
374
375
# File 'lib/model_set.rb', line 373

def default_query_type
  :sql
end

#eachObject



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/model_set.rb', line 149

def each
  num_models = ids.size
  ids.each_slice(MAX_CACHE_SIZE) do |slice_ids|
    clear_cache! if num_models > MAX_CACHE_SIZE
    fetch_models(slice_ids)
    slice_ids.each do |id|
      # Skip models that aren't in the database.
      model = models_by_id[id]
      if model
        yield model
      else
        ( @missing_ids ||= [] ) << id
      end
    end
  end
end

#each_slice(num = MAX_CACHE_SIZE) ⇒ Object



140
141
142
143
144
145
146
147
# File 'lib/model_set.rb', line 140

def each_slice(num=MAX_CACHE_SIZE)
  ids.each_slice(num) do |slice_ids|
    set = self.clone
    set.ids = slice_ids
    set.clear_cache!
    yield set
  end
end

#each_with_indexObject



166
167
168
169
170
171
172
# File 'lib/model_set.rb', line 166

def each_with_index
  i = per_page ? (current_page - 1) * per_page : 0
  each do |model|
    yield(model, i)
    i += 1
  end
end

#empty!Object



323
324
325
326
# File 'lib/model_set.rb', line 323

def empty!
  self.ids = []
  self
end

#empty?Boolean

Returns:

  • (Boolean)


303
304
305
# File 'lib/model_set.rb', line 303

def empty?
  not any?
end

#extract_opt(key, args) ⇒ Object



413
414
415
416
417
418
# File 'lib/model_set.rb', line 413

def extract_opt(key, args)
  opts = args.last.kind_of?(Hash) ? args.pop : {}
  opt  = opts.delete(key)
  args << opts unless opts.empty?
  opt
end

#first(limit = nil) ⇒ Object



112
113
114
115
116
117
118
# File 'lib/model_set.rb', line 112

def first(limit=nil)
  if limit
    self.limit(limit)
  else
    self[0]
  end
end

#idsObject



44
45
46
# File 'lib/model_set.rb', line 44

def ids
  model_ids.to_a
end

#ids=(model_ids) ⇒ Object



328
329
330
331
332
333
# File 'lib/model_set.rb', line 328

def ids=(model_ids)
  model_ids = model_ids.collect {|id| id.to_i}
  self.query = SetQuery.new(self.class)
  query.add!(model_ids)
  self
end

#in_groups_of(num) ⇒ Object



132
133
134
135
136
137
138
# File 'lib/model_set.rb', line 132

def in_groups_of(num)
  each_slice(num) do |slice_set|
    slice = slice_set.to_a
    slice[num-1] = nil if slice.size < num
    yield slice
  end
end

#include?(model) ⇒ Boolean

Returns:

  • (Boolean)


79
80
81
82
# File 'lib/model_set.rb', line 79

def include?(model)
  model_id = as_id(model)
  model_ids.include?(model_id)
end

#include_models!(*models) ⇒ Object



428
429
430
431
432
433
434
435
436
# File 'lib/model_set.rb', line 428

def include_models!(*models)
  raise 'cannot use both add_fields and include_models' if @add_fields

  # included models to pass to find call (see ActiveResource::Base.find)
  ( @included_models ||= [] ).concat(models)

  # We have to reload the models because we are adding additional fields.
  self.clear_cache!
end

#last(limit = nil) ⇒ Object



120
121
122
123
124
125
126
# File 'lib/model_set.rb', line 120

def last(limit=nil)
  if limit
    self.limit(limit, size - limit)
  else
    self[-1]
  end
end

#lazy_limit!(limit, fetch = limit * 10) ⇒ Object



409
410
411
# File 'lib/model_set.rb', line 409

def lazy_limit!(limit, fetch = limit * 10)
  anchor!(:sql, :limit_fetch => fetch).limit!(limit)
end

#marshal_dumpObject



597
598
599
# File 'lib/model_set.rb', line 597

def marshal_dump
  [ @query, @add_fields, @included_models, @created_at ]
end

#marshal_load(fields) ⇒ Object



601
602
603
# File 'lib/model_set.rb', line 601

def marshal_load(fields)
  @query, @add_fields, @included_models, @created_at = fields
end

#merge_cache!(other) ⇒ Object



448
449
450
451
452
# File 'lib/model_set.rb', line 448

def merge_cache!(other)
  other_cache = other.models_by_id
  models_by_id.merge!(other_cache)
  self
end

#missing_idsObject



48
49
50
# File 'lib/model_set.rb', line 48

def missing_ids
  ( @missing_ids || [] ).uniq
end

#older_than?(duration) ⇒ Boolean

Returns:

  • (Boolean)


40
41
42
# File 'lib/model_set.rb', line 40

def older_than?(duration)
  created_at.nil? or created_at < Time.now - duration
end

#partition_by(filter) ⇒ Object



276
277
278
279
280
281
282
283
284
285
286
# File 'lib/model_set.rb', line 276

def partition_by(filter)
  filter = filter.to_s
  filter[-1] = '' if filter =~ /\!$/
  positive = self.send(filter)
  negative = self - positive
  if block_given?
    yield(positive, negative)
  else
    [positive, negative]
  end
end

#per_pageObject

for will_paginate



311
312
313
# File 'lib/model_set.rb', line 311

def per_page # for will_paginate
  query.limit
end

#query_class(type = query.class) ⇒ Object



349
350
351
# File 'lib/model_set.rb', line 349

def query_class(type = query.class)
  type.kind_of?(Symbol) ? QUERY_TYPES[type] : type
end

#query_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


353
354
355
# File 'lib/model_set.rb', line 353

def query_type?(type)
  query_class(type) == query_class
end

#reanchor!(type = default_query_type, *args) ⇒ Object



366
367
368
369
370
371
# File 'lib/model_set.rb', line 366

def reanchor!(type = default_query_type, *args)
  # Force anchoring even if you are already anchored to this type.
  return unless type
  self.query = query_class(type).new(self, *args)
  self
end

#reject(&block) ⇒ Object



174
175
176
# File 'lib/model_set.rb', line 174

def reject(&block)
  self.clone.reject!(&block)
end

#reject!Object



178
179
180
181
182
183
184
185
# File 'lib/model_set.rb', line 178

def reject!
  filtered_ids = []
  self.each do |model|
    filtered_ids << model.send(id_field) unless yield model
  end
  self.ids = filtered_ids
  self
end

#reject_ids(&block) ⇒ Object



203
204
205
# File 'lib/model_set.rb', line 203

def reject_ids(&block)
  self.clone.select_ids!(&block)
end

#reject_ids!Object



207
208
209
210
211
212
# File 'lib/model_set.rb', line 207

def reject_ids!
  self.ids = ids.select do |id|
    not yield id
  end
  self
end

#reject_raw(&block) ⇒ Object



225
226
227
# File 'lib/model_set.rb', line 225

def reject_raw(&block)
  self.clone.reject_raw!(&block)
end

#reject_raw!(&block) ⇒ Object



229
230
231
232
233
# File 'lib/model_set.rb', line 229

def reject_raw!(&block)
  anchor!(:raw)
  query.reject!(&block)
  self
end

#secondObject



128
129
130
# File 'lib/model_set.rb', line 128

def second
  self[1]
end

#select(limit = nil, &block) ⇒ Object



187
188
189
# File 'lib/model_set.rb', line 187

def select(limit = nil, &block)
  self.clone.select!(limit, &block)
end

#select!(limit = nil) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
# File 'lib/model_set.rb', line 191

def select!(limit = nil)
  filtered_ids = []
  self.each do |model|
    if yield model
      filtered_ids << model.send(id_field)
      break if filtered_ids.size == limit
    end
  end
  self.ids = filtered_ids
  self
end

#select_ids(&block) ⇒ Object



214
215
216
# File 'lib/model_set.rb', line 214

def select_ids(&block)
  self.clone.select_ids!(&block)
end

#select_ids!Object



218
219
220
221
222
223
# File 'lib/model_set.rb', line 218

def select_ids!
  self.ids = ids.select do |id|
    yield id
  end
  self
end

#select_raw(&block) ⇒ Object



235
236
237
# File 'lib/model_set.rb', line 235

def select_raw(&block)
  self.clone.select_raw!(&block)
end

#select_raw!(&block) ⇒ Object



239
240
241
242
243
# File 'lib/model_set.rb', line 239

def select_raw!(&block)
  anchor!(:raw)
  query.select!(&block)
  self
end

#shuffle!Object



73
74
75
76
77
# File 'lib/model_set.rb', line 73

def shuffle!
  reanchor!(:set)
  query.shuffle!
  self
end

#sizeObject Also known as: length



292
293
294
# File 'lib/model_set.rb', line 292

def size
  query.size
end

#sort(&block) ⇒ Object



255
256
257
# File 'lib/model_set.rb', line 255

def sort(&block)
  self.clone.sort!(&block)
end

#sort!(&block) ⇒ Object



259
260
261
262
263
264
# File 'lib/model_set.rb', line 259

def sort!(&block)
  block ||= lambda {|a,b| a <=> b}
  sorted_ids = to_a.sort(&block).collect {|m| m.id}
  reorder!(sorted_ids)
  self
end

#sort_by(&block) ⇒ Object



266
267
268
# File 'lib/model_set.rb', line 266

def sort_by(&block)
  self.clone.sort_by!(&block)
end

#sort_by!(&block) ⇒ Object



270
271
272
273
274
# File 'lib/model_set.rb', line 270

def sort_by!(&block)
  sorted_ids = to_a.sort_by(&block).collect {|m| m.id}
  reorder!(sorted_ids)
  self
end

#sort_by_raw(&block) ⇒ Object



245
246
247
# File 'lib/model_set.rb', line 245

def sort_by_raw(&block)
  self.clone.sort_by_raw!(&block)
end

#sort_by_raw!(&block) ⇒ Object



249
250
251
252
253
# File 'lib/model_set.rb', line 249

def sort_by_raw!(&block)
  anchor!(:raw)
  query.sort_by!(&block)
  self
end

#syncObject



454
455
456
457
# File 'lib/model_set.rb', line 454

def sync
  ids
  self
end

#sync_modelsObject



459
460
461
462
463
464
# File 'lib/model_set.rb', line 459

def sync_models
  if size <= MAX_CACHE_SIZE
    fetch_models(model_ids)
  end
  self
end

#to_conditions(*args) ⇒ Object



388
389
390
391
# File 'lib/model_set.rb', line 388

def to_conditions(*args)
  anchor!( extract_opt(:query_type, args) || default_query_type )
  query.to_conditions(*args)
end

#total_entriesObject

for will_paginate



315
316
317
# File 'lib/model_set.rb', line 315

def total_entries # for will_paginate
  query.count
end

#total_pagesObject

for will_paginate



319
320
321
# File 'lib/model_set.rb', line 319

def total_pages # for will_paginate
  query.pages
end