namespace :testdo desc "テスト用データ引き当て" task :test_task => :environmentdo result = Product.joins(:product_options) .select("products.id, products.name, products.price, MAX(CASE WHEN product_options.feature_category = 'color' THEN product_options.option_value END) AS color, MAX(CASE WHEN product_options.feature_category = 'size' THEN product_options.option_value END) AS size") .group("products.id, products.name, products.price") .having("color IS NOT NULL") .having("size IS NOT NULL") pp result # => [#<Product:0x00007fc0e2c10db8 id: 1, name: "商品A", price: 0.1e4, color: "RED", size: "L">, # #<Product:0x00007fc0e5a73fc0 id: 2, name: "商品B", price: 0.2e4, color: "BLUE", size: "M">]
# 別解として全カラム埋まっていることを前提としないならシンプルに以下のように記述できる result = Product #.where(id: 1) .joins(:product_options) .select("products.id, products.name, products.price, MAX(CASE WHEN product_options.feature_category = 'color' THEN product_options.option_value END) AS color, MAX(CASE WHEN product_options.feature_category = 'size' THEN product_options.option_value END) AS size") .group("products.id") pp result # => [#<Product:0x00007fc0e2c10db8 id: 1, name: "商品A", price: 0.1e4, color: "RED", size: "L">, # #<Product:0x00007fc0e5a73fc0 id: 2, name: "商品B", price: 0.2e4, color: "BLUE", size: "M">]
result = Product .where(id:1) .joins(:product_options) .select("products.id, products.name, products.price, MAX(CASE WHEN product_options.feature_category = 'color' THEN product_options.option_value END) AS color, MAX(CASE WHEN product_options.feature_category = 'size' THEN product_options.option_value END) AS size") .group("products.id") pp result # => [#<Product:0x00007fd02d2d6e00 id: 1, name: "商品A", price: 0.1e4, color: "RED", size: "L">] end end
scope :with_product_options, -> { joins(:product_options).select("products.id, products.name, products.price, MAX(CASE WHEN product_options.feature_category = 'color' THEN product_options.option_value END) AS color, MAX(CASE WHEN product_options.feature_category = 'size' THEN product_options.option_value END) AS size").group("products.id") } end
dynamic_definition_query = feature_category.map do |category| "MAX(CASE WHEN product_options.feature_category = '#{category}' THEN product_options.option_value END) AS #{category}" end
result = Product.joins(:product_options).select("products.id, products.name, products.price, #{dynamic_definition_query.join(', ')}") .group("products.id")
scope :with_product_options, -> { joins(:product_options) .select("products.id, products.name, products.price, MAX(CASE WHEN product_options.feature_category = 'color' THEN product_options.option_value END) AS product_option_value_color, MAX(CASE WHEN product_options.feature_category = 'color' THEN product_options.value_type END) AS product_option_type_color, MAX(CASE WHEN product_options.feature_category = 'size' THEN product_options.option_value END) AS product_option_value_size, MAX(CASE WHEN product_options.feature_category = 'size' THEN product_options.value_type END) AS product_option_type_size, MAX(CASE WHEN product_options.feature_category = 'weight' THEN product_options.option_value END) AS product_option_value_weight, MAX(CASE WHEN product_options.feature_category = 'weight' THEN product_options.value_type END) AS product_option_type_weight, MAX(CASE WHEN product_options.feature_category = 'tolerance' THEN product_options.option_value END) AS product_option_value_tolerance, MAX(CASE WHEN product_options.feature_category = 'tolerance' THEN product_options.value_type END) AS product_option_type_tolerance, MAX(CASE WHEN product_options.feature_category = 'serial' THEN product_options.option_value END) AS product_option_value_serial, MAX(CASE WHEN product_options.feature_category = 'serial' THEN product_options.value_type END) AS product_option_type_serial, MAX(CASE WHEN product_options.feature_category = 'stack' THEN product_options.option_value END) AS product_option_value_stack, MAX(CASE WHEN product_options.feature_category = 'stack' THEN product_options.value_type END) AS product_option_type_stack" ) .group("products.id") }
return org_value if value_type == "string" return org_value.to_i if value_type == "integer" return get_product_option_range_value(method_name, org_value) if value_type == "range" return get_product_option_list_value(method_name, org_value) if value_type == "list" return get_product_option_script_value(method_name, org_value) if value_type == "script" end
defget_product_option_range_value(method_name, org_value) mode = if org_value.include?("...") "..." elsif org_value.include?("..") ".." else "ERROR" end raiseArgumentError, "#{method_name} is not range format"if mode == "ERROR" range_start, range_end = org_value.split(mode).map(&:strip) range_start = range_start.to_f if range_start =~ /\A\d+(\.\d+)?\z/ range_end = range_end.to_f if range_end =~ /\A\d+(\.\d+)?\z/ returnRange.new(range_start, range_end, true) if mode == "..." Range.new(range_start, range_end) end
defget_product_option_list_value(method_name, org_value) tmp = org_value.split(",").map(&:strip) raiseArgumentError, "#{method_name} is not list format"if tmp.empty?
tmp.map do |v| if v =~ /\A\d+(\.\d+)?\z/ v.to_f elsif v =~ /\A\d+\z/ v.to_i else v.tr("'", "") end end end
defget_product_option_script_value(method_name, org_value) eval(org_value) end end
moduleFeatureOption extendActiveSupport::Concern included do # 読み込まれた際にOPTION_CATEGORIESの定義が無ければエラーにする ifdefined?(base_class.name.constantize::OPTION_CATEGORIES) != "constant" raiseArgumentError, "OPTION_CATEGORIES is not defined" end
joins("#{model_name}_options".to_sym) .select("#{table_name}.*, #{get_option_query(model_name, categories)}") .group("#{table_name}.id") } end class_methods do defget_option_query(model_name, categories) target_categories = categories || base_class::OPTION_CATEGORIES table_name = "#{model_name}_options"
target_categories.map do |category| "MAX(CASE WHEN #{table_name}.feature_category = '#{category}' THEN #{table_name}.option_value END) AS #{get_option_value_key(model_name, category)}, MAX(CASE WHEN #{table_name}.feature_category = '#{category}' THEN #{table_name}.value_type END) AS #{get_option_type_key(model_name, category)}" end.join(",") end
defget_option_value_key(model_name, method_name) "#{model_name}_option_value_#{method_name}" end defget_option_type_key(model_name, method_name) "#{model_name}_option_type_#{method_name}" end end
return org_value if value_type == "string" return org_value.to_i if value_type == "integer" return get_option_range_value(method_name, org_value) if value_type == "range" return get_option_list_value(method_name, org_value) if value_type == "list" return get_option_script_value(method_name, org_value) if value_type == "script" end
defget_option_range_value(method_name, org_value) mode = if org_value.include?("...") "..." elsif org_value.include?("..") ".." else "ERROR" end raise"FeatureOption::NotRangeFormat", "#{method_name} is not range format"if mode == "ERROR" range_start, range_end = org_value.split(mode).map(&:strip) range_start = range_start.to_f if range_start =~ /\A\d+(\.\d+)?\z/ range_end = range_end.to_f if range_end =~ /\A\d+(\.\d+)?\z/ returnRange.new(range_start, range_end, true) if mode == "..." Range.new(range_start, range_end) end
defget_option_list_value(method_name, org_value) tmp = org_value.split(",").map(&:strip) raise"FeatureOption::NotListFormat", "#{method_name} is not list format, org_value: #{org_value}, #{e.message}"if tmp.empty?
string_to_value(tmp) end
defstring_to_value(string_list) string_list.map do |v| if v =~ /\A\d+(\.\d+)?\z/ v.to_f elsif v =~ /\A\d+\z/ v.to_i else v.tr("'", "") end end end
defget_option_script_value(method_name, org_value) script_name, args = org_value.split(":").map(&:strip) if args.present? args = args.split(",").map(&:strip) args = string_to_value(args) end raise"FeatureOption::NotDefinedOptionScriptMethods"unlessself.class.const_defined?(:OPTION_SCRIPT_METHODS) raise"FeatureOption::NotAllowMethod"unless allow_method_names.include?(script_name)
self.class::OPTION_SCRIPT_METHODS[script_name.to_sym].call(*args) end
defallow_method_names self.class::OPTION_SCRIPT_METHODS.keys.map(&:to_s) end end