module ArVirtualField
def virtual_field(name, scope: nil, select:, get:, default: nil)
def virtual_field(name, scope: nil, select:, get:, default: nil) @ar_virtual_fields ||= FieldsData.new name = name.to_s current_class = self if default default = HelperMethods.wrap_to_lambda(default) end select_lambda = HelperMethods.wrap_to_lambda(select) if scope field_name = Arel.sql(HelperMethods.table_with_column(name)) @ar_virtual_fields[name.to_sym] = if default -> { Arel::Nodes::NamedFunction.new('COALESCE', [field_name, default.()]) } else -> { field_name } end scope_name = :"_scope_#{name}" scope(scope_name, scope) scope(:"with_#{name}", -> do scope_query = current_class .send(scope_name) .select(select_lambda.().as(name), "#{table_name}.id") HelperMethods.select_append(joins(<<~SQL.squish), "#{HelperMethods.table_with_column(name)} AS #{name}") LEFT JOIN (#{scope_query.to_sql}) #{HelperMethods.table_name(name)} ON #{ Array(primary_key).map do |pk| "#{HelperMethods.table_name(name)}.#{pk} = #{table_name}.#{pk}" end.join(' AND') } SQL end) else if default select_lambda = -> { Arel::Nodes::NamedFunction.new('COALESCE', [select_lambda.(), default.()]) } end @ar_virtual_fields[name.to_sym] = -> { select_lambda.() } scope(:"with_#{name}", -> do HelperMethods.select_append(self, select_lambda.().as(name)) end) end method_name = :"ar_virtual_field_#{name}" define_method(method_name, &get) define_method(name) do if ActiveRecord::Base.connection.query_cache_enabled attributes.key?(name) ? self[name] : send(method_name) else send(method_name) end end end
def virtual_fields
def virtual_fields @ar_virtual_fields || FieldsData.new end