ActiveFlag

repository·master·Indexed 42 days ago

https://github.com/kenn/active_flag

A bit array implementation for ActiveRecord that allows developers to store multiple boolean flags within a single integer column in a database. It provides a `flag` macro for model definitions, bitwise query scopes (e.g., `where_`, `where_all_`), bulk update methods, and integration with Rails i18n for human-readable flag labels.

Tokens
3.7K
Snippets
16
Records
17
Agent score
53%

What's inside ActiveFlag

  1. Configure an ActiveFlag column in a migration

    master

    When creating a migration for an ActiveFlag column, it is recommended to set the default value to 0 and ensure the column is null: false.

    If you need to store more than 32 flags, you must specify limit: 8 (which allows up to 64 flags).

    t.integer :languages, null: false, default: 0, limit: 8
    # OR
    add_column :users, :languages, :integer, null: false, default: 0, limit: 8
  2. Translate ActiveFlag labels with i18n

    master

    ActiveFlag integrates with Rails i18n. You can define translations in your locale files following this structure:

    # config/locales/ja.yml
    ja:
      active_flag:
        profile:
          languages:
            english: 英語
            spanish: スペイン語
            # ...

    When translations are present, [column].to_human will return the translated strings based on the current I18n.locale.

    ja:
      active_flag:
        profile:
          languages:
            english: 英語
            spanish: スペイン語
            chinese: 中国語
            french: フランス語
            japanese: 日本語
  3. Use ActiveFlag instance methods

    master

    Once defined, the flag column returns an ActiveFlag::Value object which provides several methods for inspecting and modifying flags:

    • [column].english?: Returns true if the specific flag is set.
    • [column].set?(:flag): Returns true if the flag is set.
    • [column].unset?(:flag): Returns true if the flag is NOT set.
    • [column].set(:flag): Sets the flag.
    • [column].unset(:flag): Unsets the flag.
    • [column].raw: Returns the underlying integer value.
    • [column].to_a: Returns an array of the active flag symbols.
    • [column].to_human: Returns an array of human-readable/translated strings.
    • [column] = [:flag1, :flag2]: Direct assignment (useful for forms).
    # Assuming Profile model from example
    profile = Profile.new
    
    profile.languages.english?          #=> true
    profile.languages.set?(:english)    #=> true
    profile.languages.unset?(:english)  #=> false
    
    profile.languages.set(:spanish)
    profile.languages.unset(:japanese)
    profile.languages.raw               #=> 3
    profile.languages.to_a             #=> [:english, :spanish]
    
    # Direct assignment
    profile.languages = [:spanish, :japanese]
  4. Update ActiveFlag values in bulk via class methods

    master

    You can perform bulk updates on the database using these class-level methods:

    • [Model].languages.set_all!(:flag): Sets the specified flag for all records (using bitwise OR).
    • [Model].languages.unset_all!(:flag): Unsets the specified flag for all records (using bitwise AND NOT).
    # Sets the :chinese flag for all profiles
    Profile.languages.set_all!(:chinese)
    
    # Unsets the :chinese flag for all profiles
    Profile.languages.unset_all!(:chinese)
  5. Define ActiveFlag in an ActiveRecord model

    master

    Use the flag macro within your model to define the bit array. Pass the column name and an array of symbols representing the flags.

    class Profile < ActiveRecord::Base
      flag :languages, [:english, :spanish, :chinese, :french, :japanese]
    end
  6. Use ActiveFlag class methods

    master

    The model class provides utility methods to inspect the flag definitions:

    • [Model].languages.maps: Returns a hash mapping symbols to their bit integer values.
    • [Model].languages.humans: Returns a hash mapping symbols to their human-readable names.
    • [Model].languages.pairs: Returns a hash mapping human-readable names to symbols (ideal for form collections).
    • [Model].languages.to_array(integer): Converts a raw integer back into an array of symbols.
    # Assuming Profile model from example
    Profile.languages.maps    #=> {:english=>1, :spanish=>2, :chinese=>4, :french=>8, :japanese=>16 }
    Profile.languages.humans  #=> {:english=>"English", :spanish=>"Spanish", :chinese=>"Chinese", :french=>"French", :japanese=>"Japanese"}
    Profile.languages.pairs   #=> {"English"=>:english, "Spanish"=>:spanish, "Chinese"=>:chinese, "French"=>:french, "Japanese"=>:japanese}
    Profile.languages.to_array(3) #=> [:english, :spanish]
  7. Query records using ActiveFlag scopes

    master

    ActiveFlag provides several scopes to perform bitwise queries on your database:

    • where_[column](:flag1, :flag2): Returns records that have any of the specified flags (OR logic).
    • where_all_[column](:flag1, :flag2): Returns records that have all of the specified flags (AND logic).
    • where_not_[column](:flag1, :flag2): Returns records that have none of the specified flags.
    • where_not_all_[column](:flag1, :flag2): Returns records that do not have all of the specified flags.

    Note: These scopes accept multiple arguments to perform bitwise operations on the combined flags.

    # Returns profiles that have either French or Spanish
    Profile.where_languages(:french, :spanish)
    
    # Returns profiles that have both French and Spanish
    Profile.where_all_languages(:french, :spanish)
    
    # Returns profiles that do not have either French or Spanish
    Profile.where_not_languages(:french, :spanish)
    
    # Returns profiles that do not have both French and Spanish
    Profile.where_not_all_languages(:french, :spanish)
  8. Query flags using `where_` scopes

    master

    ActiveFlag provides several class-level scopes to query records based on their bitmask values. These scopes use bitwise AND operations in the underlying SQL.

    • where_<column>(*keys): Returns records that have at least one of the specified keys.
    • where_all_<column>(*keys): Returns records that have all of the specified keys.
    • where_not_<column>(*keys): Returns records that have none of the specified keys.
    • where_not_all_<column>(*keys): Returns records that do not have all of the specified keys (i.e., they might have some, but not all).
    class User < ActiveRecord::Base
      flag :roles, { admin: 1, editor: 2, viewer: 4 }
    end
    
    # Find users who are either admins or editors
    User.where_roles(:admin, :editor)
    
    # Find users who are both admins and editors
    User.where_all_roles(:admin, :editor)
    
    # Find users who are neither admins nor editors
    User.where_not_roles(:admin, :editor)
    
    # Find users who are not (both admin and editor)
    User.where_not_all_roles(:admin, :editor)
  9. Convert flag keys to integer bitmasks

    master

    The to_i(arg) method converts one or more flag keys into their corresponding integer bitmask value. It accepts a single key or an Enumerable of keys. Keys are converted to symbols before lookup. If a key does not exist in the definition, it contributes 0 to the sum.

    # Example of converting keys to a bitmask
    mask = definition.to_i([:english, :spanish])
  10. Define flags in an ActiveRecord model using `flag`

    master

    To use ActiveFlag, call the flag macro within your ActiveRecord model. You must provide the database column name (which should be an integer type) and a hash of keys where the key is the label and the value is the bitwise integer value.

    This macro generates:

    • A getter method named after the column that returns the active keys.
    • A setter method named after the column that accepts keys or an array of keys.
    • A relation method (the pluralized column name) to access the flag definition.
    • Several where_ scopes for querying the bitmask.
    class User < ActiveRecord::Base
      flag :roles, { admin: 1, editor: 2, viewer: 4 }
    end
    
    user = User.new
    user.roles = [:admin, :viewer]
    user.roles # => [:admin, :viewer]
    user.roles_id # => 5 (the bitwise sum of 1 and 4)
  11. Access flag definitions via relation methods

    master

    When you define a flag on a column, ActiveFlag generates a singleton method on the model class named after the pluralized column name. This method returns the ActiveFlag::Definition object for that column, allowing you to inspect the mapping or use it with scopes.

    # If you define `flag :roles, ...` 
    # You can access the definition via `roles` on the class
    User.roles # => #<ActiveFlag::Definition ...>