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: 0, waiting_for_payment: 1, payed: 2, ready_for_shipment: 3, on_delivery: 4, delivered: 5 }

end

class Order

has_many :requests

end

Constant Summary collapse

VERSION =
"0.4.5"

Instance Method Summary collapse

Instance Method Details

#enum_i(enum_name) ⇒ Object

defines shortcut for getting integer value of enum. for enum named ‘status’ will generate: instance.status_i



19
20
21
22
23
# File 'lib/enum_ext.rb', line 19

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

#ext_enum_sets(enum_name, options = {}) ⇒ Object

Rem:

ext_enum_sets can be called twice defining a superposition of already defined sets ( considering previous example ):
ext_enum_sets :status, {
                outside_wharehouse: ( delivery_set_statuses - in_warehouse_statuses )... any other array operations like &, + and so can be used
              }


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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/enum_ext.rb', line 72

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

  self.instance_eval do
    define_singleton_method("ext_#{enum_plural}") do
      @enum_ext_summary ||= {}
    end unless respond_to?("ext_#{enum_plural}")

    send("ext_#{enum_plural}").merge!( options )

    # with_enums scope
    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 )
    } if !respond_to?("with_#{enum_plural}") && respond_to?(:scope)

    # without_enums scope
    scope "without_#{enum_plural}", -> (sets_arr) {
      where.not( id: self.send("with_#{enum_plural}", sets_arr) )
    } if !respond_to?("without_#{enum_plural}") && respond_to?(:scope)

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

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

      # class.enum_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

      # t_... - are translation dependent methods
      # class.t_enums_options
      define_singleton_method( "t_#{set_name}_#{enum_plural}_options" ) do
        return [["Enum translations call missed. Did you forget to call translate #{enum_name}"]*2] unless respond_to?( "t_#{enum_plural}_options_raw" )

        send("t_#{enum_plural}_options_raw", send("t_#{set_name}_#{enum_plural}") )
      end

      # class.t_enums_options_i
      define_singleton_method( "t_#{set_name}_#{enum_plural}_options_i" ) do
        return [["Enum translations call missed. Did you forget to call translate #{enum_name}"]*2] unless respond_to?( "t_#{enum_plural}_options_raw_i" )

        send("t_#{enum_plural}_options_raw_i", send("t_#{set_name}_#{enum_plural}") )
      end

      # instance.set_name?
      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

      # protected?
      # class.t_setname_enums ( translations or humanizations subset for a given set )
      define_singleton_method( "t_#{set_name}_#{enum_plural}" ) do
        return [(["Enum translations call missed. Did you forget to call translate #{enum_name}"]*2)].to_h unless respond_to?( "t_#{enum_plural}" )

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

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

human_attribute_name is redefined for automation like this: p #attr_name ): p object.send(attr_name)



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

def human_attribute_name( name, options = {} )
  # if name starts from t_ and there is a column with the last part then ...
  name[0..1] == 't_' && column_names.include?(name[2..-1]) ? super( name[2..-1], options ) : super( name, options )
end

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

if app doesn’t need internationalization, it may use humanize_enum to make enum user friendly class Request humanize_enum :status, {

  #locale dependent example with pluralization and lambda:
  payed: -> (t_self) { I18n.t("request.status.payed", count: t_self.sum ) }

  #locale dependent example with pluralization and proc:
  payed: Proc.new{ I18n.t("request.status.payed", count: self.sum ) }

  #locale independent:
  ready_for_shipment: "Ready to go!"
}

end

Could be called multiple times, all humanization definitions will be merged under the hood:

humanize_enum :status, {
   payed: I18n.t("scope.#{status}")

} humanize_enum :status,

billed: I18n.t("scope.#{status")

}

Example with block:

humanize_enum :status do

I18n.t("scope.#status")

end

in views select:

f.select :status, Request.t_statuses_options

in select in Active Admin filter

collection: Request.t_statuses_options_i

Rem: select options breaks when using lambda() with params

Console:

request.sum = 3
request.payed!
request.status     # >> payed
request.t_status   # >> "Payed 3 dollars"
Request.t_statuses # >> { in_cart: -> { I18n.t("request.status.in_cart") }, ....  }


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

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

  self.instance_eval do

    #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

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

    #t_enums_options
    define_singleton_method( "t_#{enum_plural}_options" ) do
      send("t_#{enum_plural}_options_raw", send("t_#{enum_plural}") )
    end

    #t_enums_options_i
    define_singleton_method( "t_#{enum_plural}_options_i" ) do
      send("t_#{enum_plural}_options_raw_i", send("t_#{enum_plural}") )
    end

    define_method "t_#{enum_name}=" do |new_val|
      send("#{enum_name}=", new_val)
    end

    #protected?
    define_singleton_method( "t_#{enum_plural}_options_raw_i" ) do |t_enum_set|
      send("t_#{enum_plural}_options_raw", t_enum_set ).map do | key_val |
        key_val[1] = send(enum_plural)[key_val[1]]
        key_val
      end
    end

    define_singleton_method( "t_#{enum_plural}_options_raw" ) do |t_enum_set|
      t_enum_set.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
        if key_val[0].respond_to?(:call)
          if key_val[0].try(:arity) < 1
            key_val[0] = key_val[0].try(:call) rescue "Cannot create option for #{key_val[1]} ( proc fails to evaluate )"
          else
            key_val[0] = "Cannot create option for #{key_val[1]} because of a lambda"
          end
        end
        key_val
      end
    end
  end
end

#mass_assign_enum(*enums_names) ⇒ Object

Ex mass_assign_enum Used for mass assigning for collection without callbacks it creates bang methods for collections using update_all. it’s often case when you need bulk update without callbacks, so it’s gets frustrating to repeat: some_scope.update_all(status: Request.statuses, update_at: Time.now) If you need callbacks you can do like this: some_scope.each(&:new_stat!) but if you don’t need callbacks and you have lots of records to change at once you need update_all

mass_assign_enum( :status )

class methods:
  in_cart! paid! in_warehouse! and so

Console: request1.in_cart! request2.waiting_for_payment! Request.with_statuses( :in_cart, :waiting_for_payment ).payed! request1.paid? # >> true request2.paid? # >> true request1.updated_at # >> Time.now defined?(Request::MassAssignEnum) # >> true

order.requests.paid.all?(&:paid?) # >> true order.requests.paid.delivered! order.requests.map(&:status).uniq #>> [:delivered]



163
164
165
166
167
168
169
170
171
172
173
# File 'lib/enum_ext.rb', line 163

def mass_assign_enum( *enums_names )
  enums_names.each do |enum_name|
    enum_vals = self.send( enum_name.to_s.pluralize )

    enum_vals.keys.each do |enum_el|
      define_singleton_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
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



291
292
293
294
295
296
297
298
299
300
301
# File 'lib/enum_ext.rb', line 291

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

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