Module: FlagShihTzu::ClassMethods

Defined in:
lib/flag_shih_tzu.rb

Instance Method Summary collapse

Instance Method Details

#chained_flags_condition(colmn = DEFAULT_COLUMN_NAME, *args) ⇒ Object



527
528
529
530
531
532
533
534
535
536
537
538
539
540
# File 'lib/flag_shih_tzu.rb', line 527

def chained_flags_condition(colmn = DEFAULT_COLUMN_NAME, *args)
  if flag_options[colmn][:flag_query_mode] == :bit_operator
    conditions = args.map do |flag|
      flag, enabled = flag_enabled_query(flag)
      check_flag(flag, colmn)
      flag_value = flag_mapping[colmn][flag]
      expected_value = flag_encoder_for_column(colmn).sql_value_for(flag_value, enabled)
      "#{flag_full_column_name(table_name, colmn)} & #{flag_value} = #{expected_value}"
    end
    return %[(#{conditions.join(" AND ")})]
  end

  %[(#{flag_full_column_name(table_name, colmn)} in (#{chained_flags_values(colmn, *args).join(",")}))]
end

#chained_flags_with(column = DEFAULT_COLUMN_NAME, *args) ⇒ Object



519
520
521
522
523
524
525
# File 'lib/flag_shih_tzu.rb', line 519

def chained_flags_with(column = DEFAULT_COLUMN_NAME, *args)
  if ActiveRecord::VERSION::MAJOR >= 3
    where(chained_flags_condition(column, *args))
  else
    all(conditions: chained_flags_condition(column, *args))
  end
end

#check_flag(flag, colmn) ⇒ Object



491
492
493
494
495
496
497
498
499
500
# File 'lib/flag_shih_tzu.rb', line 491

def check_flag(flag, colmn)
  unless colmn.is_a?(String)
    raise ArgumentError,
      %[Column name "#{colmn}" for flag "#{flag}" is not a string]
  end
  if flag_mapping[colmn].nil? || !flag_mapping[colmn].include?(flag)
    raise ArgumentError,
      %[Invalid flag "#{flag}"]
  end
end

#determine_flag_colmn_for(flag) ⇒ Object



509
510
511
512
513
514
515
516
517
# File 'lib/flag_shih_tzu.rb', line 509

def determine_flag_colmn_for(flag)
  return DEFAULT_COLUMN_NAME if flag_mapping.nil?
  flag_mapping.each_pair do |colmn, mapping|
    return colmn if mapping.include?(flag)
  end
  raise NoSuchFlagException.new(
    %[determine_flag_colmn_for: Couldn't determine column for your flags!],
  )
end

#flag_keys(colmn = DEFAULT_COLUMN_NAME) ⇒ Object



542
543
544
# File 'lib/flag_shih_tzu.rb', line 542

def flag_keys(colmn = DEFAULT_COLUMN_NAME)
  flag_mapping[colmn].keys
end

#has_flags(*args) ⇒ Object



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/flag_shih_tzu.rb', line 213

def has_flags(*args)
  flag_hash, opts = parse_flag_options(*args)
  opts =
    {
      named_scopes: true,
      column: DEFAULT_COLUMN_NAME,
      flag_query_mode: FlagShihTzu.default_flag_query_mode,
      strict: false,
      check_for_column: FlagShihTzu.default_check_for_column,
      allow_overwrite: false,
      value_mode: nil,
      bit_width: nil,
    }.update(opts)
  opts[:value_mode], opts[:bit_width], opts[:encoder] =
    flag_encoder_config(opts[:value_mode], opts[:bit_width], opts[:encoder])
  if !valid_flag_column_name?(opts[:column])
    warn(%[FlagShihTzu says: Please use a String to designate column names! I see you here: #{caller(1..1).first}])
    opts[:column] = opts[:column].to_s
  end
  colmn = opts[:column]
  if opts[:check_for_column] && active_record_class? && !check_flag_column(colmn)
    warn(
      %[FlagShihTzu says: Flag column #{colmn} appears to be missing!
To turn off this warning set check_for_column: false in has_flags definition here: #{caller(1..1).first}],
    )
    return
  end

  # options are stored in a class level hash and apply per-column
  self.flag_options ||= {}
  flag_options[colmn] = opts

  # the mappings are stored in this class level hash and apply per-column
  self.flag_mapping ||= {}
  # If we already have an instance of the same column in the flag_mapping,
  #   then there is a double definition on a column
  if opts[:strict] && !self.flag_mapping[colmn].nil?
    raise DuplicateFlagColumnException
  end
  flag_mapping[colmn] ||= {}

  # keep track of which flag columns are defined on this class
  self.flag_columns ||= []
  self.flag_columns << colmn

  flag_hash.each do |flag_key, flag_name|
    unless valid_flag_key?(flag_key)
      raise ArgumentError,
        %[has_flags: flag keys should be positive integers, and #{flag_key} is not]
    end
    unless valid_flag_name?(flag_name)
      raise ArgumentError,
        %[has_flags: flag names should be symbols, and #{flag_name} is not]
    end
    flag_mask = opts[:encoder].mask(flag_key)
    # next if method already defined by flag_shih_tzu
    next if flag_mapping[colmn][flag_name] & flag_mask
    if method_defined?(flag_name)
      if opts[:allow_overwrite]
        remove_existing_flag_methods(flag_name)
      else
        raise ArgumentError,
          %[has_flags: flag name #{flag_name} already defined, please choose different name]
      end
    end

    flag_mapping[colmn][flag_name] = flag_mask

    class_eval(<<-EVAL, __FILE__, __LINE__ + 1)
      def #{flag_name}
        flag_enabled?(:#{flag_name}, "#{colmn}")
      end
      alias :#{flag_name}? :#{flag_name}

      def #{flag_name}=(value)
        set_flag_value(:#{flag_name}, value, "#{colmn}")
      end

      def not_#{flag_name}
        flag_disabled?(:#{flag_name}, "#{colmn}")
      end
      alias :not_#{flag_name}? :not_#{flag_name}

      def not_#{flag_name}=(value)
        set_flag_value(:#{flag_name}, inverse_flag_value(value, "#{colmn}"), "#{colmn}")
      end

      def #{flag_name}_changed?
        if colmn_changes = changes["#{colmn}"]
          flag_bit = self.class.flag_mapping["#{colmn}"][:#{flag_name}]
          self.class.send(:flag_encoder_for_column, "#{colmn}").changed?(colmn_changes[0], colmn_changes[1], flag_bit)
        else
          false
        end
      end

      def #{flag_name}_nil?
        flag_enabled?(:#{flag_name}, "#{colmn}").nil?
      end

    EVAL

    if active_record_class?
      class_eval(<<-EVAL, __FILE__, __LINE__ + 1)
        def self.#{flag_name}_condition(options = {})
          sql_condition_for_flag(
            :#{flag_name},
            "#{colmn}",
            true,
            options[:table_alias] || table_name
          )
        end

        def self.not_#{flag_name}_condition(options = {})
          sql_condition_for_flag(
            :#{flag_name},
            "#{colmn}",
            false,
            options[:table_alias] || table_name
          )
        end

        def self.#{flag_name}_nil_condition(options = {})
          sql_condition_for_flag(
            :#{flag_name},
            "#{colmn}",
            nil,
            options[:table_alias] || table_name
          )
        end

        def self.set_#{flag_name}_sql
          sql_set_for_flag(:#{flag_name}, "#{colmn}", true)
        end

        def self.unset_#{flag_name}_sql
          sql_set_for_flag(:#{flag_name}, "#{colmn}", false)
        end

        def self.clear_#{flag_name}_sql
          sql_set_for_flag(:#{flag_name}, "#{colmn}", nil)
        end
      EVAL

      # Define the named scopes if the user wants them and AR supports it
      if flag_options[colmn][:named_scopes]
        if ActiveRecord::VERSION::MAJOR == 2 && respond_to?(:named_scope)
          class_eval(<<-EVAL, __FILE__, __LINE__ + 1)
            named_scope :#{flag_name}, lambda {
              { conditions: #{flag_name}_condition }
            }
            named_scope :not_#{flag_name}, lambda {
              { conditions: not_#{flag_name}_condition }
            }
          EVAL
        elsif respond_to?(:scope)
          # Prevent deprecation notices on Rails 3
          #   when using +named_scope+ instead of +scope+.
          # Prevent deprecation notices on Rails 4
          #   when using +conditions+ instead of +where+.
          class_eval(<<-EVAL, __FILE__, __LINE__ + 1)
            scope :#{flag_name}, lambda {
              where(#{flag_name}_condition)
            }
            scope :not_#{flag_name}, lambda {
              where(not_#{flag_name}_condition)
            }
            scope :#{flag_name}_nil, lambda {
              where(#{flag_name}_nil_condition)
            }
          EVAL
        end
      end

      if method_defined?(:saved_changes)
        class_eval(<<-EVAL, __FILE__, __LINE__ + 1)
          def saved_change_to_#{flag_name}?
            if colmn_changes = saved_changes["#{colmn}"]
              flag_bit = self.class.flag_mapping["#{colmn}"][:#{flag_name}]
              self.class.send(:flag_encoder_for_column, "#{colmn}").changed?(colmn_changes[0], colmn_changes[1], flag_bit)
            else
              false
            end
          end
        EVAL
      end
    end

    # Define bang methods when requested
    if flag_options[colmn][:bang_methods]
      class_eval(<<-EVAL, __FILE__, __LINE__ + 1)
        def #{flag_name}!
          enable_flag(:#{flag_name}, "#{colmn}")
        end

        def not_#{flag_name}!
          disable_flag(:#{flag_name}, "#{colmn}")
        end

        def clear_#{flag_name}!
          clear_flag(:#{flag_name}, "#{colmn}")
        end
      EVAL
    end
  end

  if colmn != DEFAULT_COLUMN_NAME
    class_eval(<<-EVAL, __FILE__, __LINE__ + 1)

      def all_#{colmn}
        all_flags("#{colmn}")
      end

      def #{colmn}=(value)
        set_flags(value, "#{colmn}")
      end

      def selected_#{colmn}
        selected_flags("#{colmn}")
      end

      def select_all_#{colmn}
        select_all_flags("#{colmn}")
      end

      def unselect_all_#{colmn}
        unselect_all_flags("#{colmn}")
      end

      # useful for a form builder
      def selected_#{colmn}=(chosen_flags)
        unselect_all_flags("#{colmn}")
        return if chosen_flags.nil?
        chosen_flags.each do |selected_flag|
          enable_flag(selected_flag.to_sym, "#{colmn}") if selected_flag.present?
        end
      end

      def has_#{colmn.singularize}?
        not selected_#{colmn}.empty?
      end

      def chained_#{colmn}_with_signature(*args)
        chained_flags_with_signature("#{colmn}", *args)
      end

      def as_#{colmn.singularize}_collection(*args)
        as_flag_collection("#{colmn}", *args)
      end

      def #{colmn}_as_attributes(*args)
        flags_as_attributes("#{colmn}", *args)
      end

    EVAL
  end

  if active_record_class?
    class_eval(<<-EVAL, __FILE__, __LINE__ + 1)
      def self.#{colmn.singularize}_values_for(*flag_names)
        values = []
        flag_names.each do |flag_name|
          if respond_to?(flag_name)
            values_for_flag = send(:sql_in_for_flag, flag_name, "#{colmn}")
            values = if values.present?
              values & values_for_flag
            else
              values_for_flag
            end
          end
        end

        values.sort
      end
    EVAL
  end
end

#set_flag_sql(flag, value, colmn = nil, custom_table_name = table_name) ⇒ Object

Returns SQL statement to enable/disable flag. Automatically determines the correct column.



504
505
506
507
# File 'lib/flag_shih_tzu.rb', line 504

def set_flag_sql(flag, value, colmn = nil, custom_table_name = table_name)
  colmn = determine_flag_colmn_for(flag) if colmn.nil?
  sql_set_for_flag(flag, colmn, value, custom_table_name)
end