Module: EnumExt

Defined in:
lib/enum_ext.rb,
lib/enum_ext/version.rb

Overview

Let’s assume we have model Request with enum status, and we have model Order with requests like this: class Request

extend EnumExt
belongs_to :order
enum status: [ :in_cart, :waiting_for_payment, :payed, :ready_for_shipment, :on_delivery, :delivered ]

end

class Order

has_many :requests

end

Constant Summary collapse

VERSION =
"0.2.1"

Instance Method Summary collapse

Instance Method Details

#enum_i(enum_name) ⇒ Object



16
17
18
19
20
# File 'lib/enum_ext.rb', line 16

def enum_i( enum_name )
  define_method "#{enum_name}_i" do
    self.class.send("#{enum_name.to_s.pluralize}")[send(enum_name)].to_i
  end
end

#enum_translated?(name) ⇒ Boolean

helper to determine is attribute is translated enum

Returns:

  • (Boolean)


287
288
289
# File 'lib/enum_ext.rb', line 287

def enum_translated?( name )
  translated_enums.include?( name.to_sym )
end

#ext_enum_sets(enum_name, options) ⇒ Object

Rem:

ext_enum_sets can be called twice defining a superpositoin of already defined sets:
class Request
  ...
  ext_enum_sets (... first time call )
  ext_enum_sets :status, {
                    already_payed: ( [:payed] | delivery_set_statuses ),
                    outside_wharehouse: ( delivery_set_statuses - in_warehouse_statuses )... any other array operations like &, + and so can be used
                 }


63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/enum_ext.rb', line 63

def ext_enum_sets( enum_name, options )
  enum_plural = enum_name.to_s.pluralize

  self.instance_eval do
    options.each do |set_name, enum_vals|
      scope set_name, -> { where( enum_name => self.send( enum_plural ).slice( *enum_vals.map(&:to_s) ).values ) }


      define_singleton_method( "#{set_name}_#{enum_plural}" ) do
        enum_vals
      end

      # set?
      define_method "#{set_name}?" do
        self.send(enum_name) && ( enum_vals.include?( self.send(enum_name) ) || enum_vals.include?( self.send(enum_name).to_sym ))
      end

      # t_set_enums
      define_singleton_method( "t_#{set_name}_#{enum_plural}" ) do
        send( "t_#{enum_plural}" ).slice( *self.send("#{set_name}_#{enum_plural}") )
      end

      # t_set_enums_options
      define_singleton_method( "t_#{set_name}_#{enum_plural}_options" ) do
        send( "t_#{set_name}_#{enum_plural}" ).invert.to_a.map do | key_val |
          key_val[0] = key_val[0].call if key_val[0].respond_to?(:call) && key_val[0].try(:arity) < 1
          key_val
        end
      end

      # set_enums_i
      define_singleton_method( "#{set_name}_#{enum_plural}_i" ) do
        self.send( "#{enum_plural}" ).slice( *self.send("#{set_name}_#{enum_plural}") ).values
      end

    end

    scope "with_#{enum_plural}", -> (sets_arr) {
      where( enum_name => self.send( enum_plural ).slice(
                 *sets_arr.map{|set_name| self.try( "#{set_name}_#{enum_plural}" ) || set_name }.flatten.uniq.map(&:to_s) ).values )
    } unless respond_to?("with_#{enum_plural}")

    scope "without_#{enum_plural}", -> (sets_arr) {
      where.not( id: self.send("with_#{enum_plural}", sets_arr) )
    } unless respond_to?("without_#{enum_plural}")
  end
end

#human_attribute_name(name, options = {}) ⇒ Object

It useful for Active Admin, since it use by default human_attribute_name to translate or humanize elements, if no translation given. So when enums translated it breaks default human_attribute_name since it’s search I18n scope from



282
283
284
# File 'lib/enum_ext.rb', line 282

def human_attribute_name( name, options = {} )
  enum_translated?(name) ? super( "t_#{name}", options ) : super( name, options )
end

#humanize_enum(*args, &block) ⇒ Object Also known as: localize_enum

if you need some substitution you can go like this

localize_enum :status, {
      ..
 delivered: "Delivered at: %{date}"
}
request.delivered!
request.t_status % {date: Time.now.to_s} # >> Delivered at: 05.02.2016

Using in select:

f.select :status, Request.t_statuses_options

Rem: select options breaks when using lambda



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
# File 'lib/enum_ext.rb', line 224

def humanize_enum( *args, &block )
  enum_name = args.shift
  localizations = args.pop
  enum_pural = enum_name.to_s.pluralize

  self.instance_eval do

    #t_enums
    define_singleton_method( "t_#{enum_pural}" ) do
      # if localization is abscent than block must be given
      localizations.try(:with_indifferent_access) || localizations ||
          send(enum_pural).keys.map {|en| [en, self.new( {enum_name => en} ).send("t_#{enum_name}")] }.to_h.with_indifferent_access
    end

    #t_enums_options
    define_singleton_method( "t_#{enum_pural}_options" ) do
      send("t_#{enum_pural}").invert.to_a.map do | key_val |
        # since all procs in t_enum are evaluated in context of a record than it's not always possible to create select options
        key_val[0] = ( key_val[0].try(:call) || "Cannot create option for #{key_val[0]}" ) if key_val[0].respond_to?(:call) && key_val[0].try(:arity) < 1
        key_val
      end
    end

    #t_enum
    define_method "t_#{enum_name}" do
      t = block || localizations.try(:with_indifferent_access)[send(enum_name)]
      if t.try(:lambda?)
        t.try(:arity) == 1 && t.call( self ) || t.try(:call)
      elsif t.is_a?(Proc)
        instance_eval(&t)
      else
        t
      end.to_s
    end
  end
end

#mass_assign_enum(*options) ⇒ Object

association_relation: true - Order.first.requests.scope.new_stat! - works but it wouldn’t works without ‘scope’ part! If you want to use it without ‘scope’ you may do it this way: class Request

...
mass_assign_enum( :status, association_relation: false )

end class Order

has_many :requests, extend: Request::MassAssignEnum

end

Order.first.requests.respond_to?(:in_cart!) # >> true

Rem2: you can mass-assign more than one enum ::MassAssignEnum module will contain mass assign for both. It will break nothing since all enum name must be uniq across model



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/enum_ext.rb', line 159

def mass_assign_enum( *options )
  relation_options = (options[-1].is_a?(Hash) && options.pop || {relation: true, association_relation: true} ).with_indifferent_access
  enums_names = options
  enums_names.each do |enum_name|
    enum_vals = self.send( enum_name.to_s.pluralize )

    mass_ass_module = ( defined?(self::MassAssignEnum) && self::MassAssignEnum || Module.new )

    mass_ass_module.instance_eval do
      enum_vals.keys.each do |enum_el|
        define_method( "#{enum_el}!" ) do
          self.update_all( {enum_name => enum_vals[enum_el]}.merge( self.column_names.include?('updated_at') ? {updated_at: Time.now} : {} ))
        end
      end
    end
    self.const_set( :MassAssignEnum, mass_ass_module ) unless defined?(self::MassAssignEnum)

    self::ActiveRecord_Relation.include( self::MassAssignEnum ) if relation_options[:relation]
    self::ActiveRecord_AssociationRelation.include( self::MassAssignEnum ) if relation_options[:association_relation]
  end
end

#translate_enum(*args, &block) ⇒ Object

Simple way to translate enum. It use either given scope as second argument, or generated activerecord.attributes.model_name_underscore.enum_name If block is given than no scopes are taken in consider



265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/enum_ext.rb', line 265

def translate_enum( *args, &block )
  enum_name = args.shift
  t_scope = args.pop || "activerecord.attributes.#{self.name.underscore}.#{enum_name}"

  translated_enums << enum_name.to_sym

  if block_given?
    humanize_enum( enum_name, &block )
  else
    humanize_enum( enum_name, send(enum_name.to_s.pluralize).keys.map{|en| [ en, Proc.new{ I18n.t("#{t_scope}.#{en}") }] }.to_h )
  end

end