-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
595 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
require "redis_counters/dumpers/version" | ||
require 'redis_counters/dumpers/version' | ||
|
||
module RedisCounters | ||
module Dumpers | ||
# Your code goes here... | ||
autoload :Engine, 'redis_counters/dumpers/engine' | ||
autoload :Destination, 'redis_counters/dumpers/destination' | ||
autoload :List, 'redis_counters/dumpers/list' | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
# coding: utf-8 | ||
require 'forwardable' | ||
require 'active_support/core_ext/hash/indifferent_access' | ||
require_relative 'dsl/destination' | ||
|
||
module RedisCounters | ||
module Dumpers | ||
# Класс представляет конечную точку сохранения данных счетчика. | ||
# | ||
# Описывает в какую модель (таблицу), какие поля имеющиеся в распоряжении дампера, | ||
# должны быть сохранены и каким образом. | ||
# | ||
# По сути, мерджит указанные поля из temp - таблицы, дампера | ||
# в указанную таблицу. | ||
# | ||
# Может использоваться как напрямую так и с помощью DSL (см. модуль RedisCounters::Dumpers::Dsl::Destination). | ||
class Destination | ||
extend Forwardable | ||
include ::RedisCounters::Dumpers::Dsl::Destination | ||
|
||
# Ссылка на родительский движек - дампер. | ||
attr_accessor :engine | ||
|
||
# Модель, в таблицу, которой будет производится мердж данных, AR::Model. | ||
attr_accessor :model | ||
|
||
# Список полей, из доступных дамперу, которые необходимо сохранить, Array. | ||
attr_accessor :fields | ||
|
||
# Список полей, по комбинации которых, будет происходить определение существования записи, | ||
# при мердже данных, Array. | ||
attr_accessor :key_fields | ||
|
||
# Список полей, которые будет инкрементированы при обновлении существующей записи, Array. | ||
attr_accessor :increment_fields | ||
|
||
# Карта полей - карта псевдонимов полей, Hash. | ||
# Названия полей в целевой таблице, могут отличаться от названий полей дампера. | ||
# Для сопоставления полей целевой таблицы и дампера, необходимо заполнить карту соответствия. | ||
# Карта, заполняется только для тех полей, названия которых отличаются. | ||
# Во всех свойствах, содержащий указания полей: fields, key_fields, increment_fields, conditions | ||
# используются имена конечных полей целевой таблицы. | ||
# | ||
# Example: | ||
# fields_map = {:pages => :value, :date => :start_month_date} | ||
# | ||
# Означает, что целевое поле :pages, указывает на поле :value, дампера, | ||
# а целевое поле :date, указывает на поле :start_month_date, дампера. | ||
attr_accessor :fields_map | ||
|
||
# Список дополнительных условий, которые применяются при обновлении целевой таблицы, Array of String. | ||
# Каждое условие представляет собой строку - часть SQL выражения, которое может включать именованные | ||
# параметры из числа доступных в хеше оббщих параметров дампера: engine.common_params. | ||
# Условия соеденяются через AND. | ||
attr_accessor :conditions | ||
|
||
def initialize(engine) | ||
@engine = engine | ||
@fields_map = HashWithIndifferentAccess.new | ||
@conditions = [] | ||
end | ||
|
||
def merge | ||
target_fields = fields.join(', ') | ||
|
||
sql = <<-SQL | ||
WITH | ||
source AS | ||
( | ||
SELECT #{selected_fields_expression} | ||
FROM #{source_table} | ||
), | ||
updated AS | ||
( | ||
UPDATE #{target_table} target | ||
SET | ||
#{updating_expression} | ||
FROM source | ||
WHERE #{matching_expression} | ||
#{extra_conditions} | ||
RETURNING target.* | ||
) | ||
INSERT INTO #{target_table} (#{target_fields}) | ||
SELECT #{target_fields} | ||
FROM source | ||
WHERE NOT EXISTS ( | ||
SELECT 1 | ||
FROM updated target | ||
WHERE #{matching_expression} | ||
#{extra_conditions} | ||
) | ||
SQL | ||
|
||
sql = model.send(:sanitize_sql, [sql, engine.common_params]) | ||
connection.execute sql | ||
end | ||
|
||
def_delegator :model, :connection | ||
def_delegator :model, :quoted_table_name, :target_table | ||
def_delegator :engine, :temp_table_name, :source_table | ||
|
||
protected | ||
|
||
def selected_fields_expression | ||
full_fields_map.map { |target_field, source_field| "#{source_field} as #{target_field}" }.join(', ') | ||
end | ||
|
||
def full_fields_map | ||
fields_map.reverse_merge(Hash[fields.zip(fields)]) | ||
end | ||
|
||
def updating_expression | ||
increment_fields.map { |field| "#{field} = COALESCE(target.#{field}, 0) + source.#{field}" }.join(', ') | ||
end | ||
|
||
def matching_expression | ||
source_key_fields = key_fields.map { |field| "source.#{field}" }.join(', ') | ||
target_key_fields = key_fields.map { |field| "target.#{field}" }.join(', ') | ||
"(#{source_key_fields}) = (#{target_key_fields})" | ||
end | ||
|
||
def extra_conditions | ||
result = conditions.map { |condition| "(#{condition})" }.join(' AND ') | ||
result.present? ? "AND #{result}" : result | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# coding: utf-8 | ||
module RedisCounters | ||
module Dumpers | ||
module Dsl | ||
# Базовый класс для создания DSL к другим классам. | ||
# Класс обертка, который имеет все свойства, включая callbacks, | ||
# который просто настраивает целевой класс через его стандартные свойства. | ||
# Профит в простоте реализации DSL, в его изоляции от основного класса, | ||
# в разделении логики основного класса и DSL к нему. | ||
class Base | ||
attr_accessor :target | ||
|
||
class << self | ||
def setter(*method_names) | ||
method_names.each do |name| | ||
send :define_method, name do |data| | ||
target.send "#{name}=".to_sym, data | ||
end | ||
end | ||
end | ||
|
||
def varags_setter(*method_names) | ||
method_names.each do |name| | ||
send :define_method, name do |*data| | ||
target.send "#{name}=".to_sym, data.flatten | ||
end | ||
end | ||
end | ||
|
||
def callback_setter(*method_names) | ||
method_names.each do |name| | ||
send :define_method, name do |method = nil, &block| | ||
target.send "#{name}=".to_sym, method, &block | ||
end | ||
end | ||
end | ||
end | ||
|
||
def initialize(target, &block) | ||
@target = target | ||
instance_eval(&block) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# coding: utf-8 | ||
require 'active_support/concern' | ||
require_relative 'base' | ||
|
||
module RedisCounters | ||
module Dumpers | ||
module Dsl | ||
# Модуль реализующий DSL для класса Destination | ||
module Destination | ||
extend ActiveSupport::Concern | ||
|
||
class Configuration < ::RedisCounters::Dumpers::Dsl::Base | ||
alias_method :destination, :target | ||
|
||
setter :model | ||
|
||
varags_setter :fields | ||
varags_setter :key_fields | ||
varags_setter :increment_fields | ||
|
||
alias_method :take, :fields | ||
|
||
def map(field, target_field) | ||
destination.fields_map.merge!(field.to_sym => target_field[:to]) | ||
end | ||
|
||
def condition(value) | ||
destination.conditions << value | ||
end | ||
end | ||
|
||
module ClassMethods | ||
def build(engine, &block) | ||
destination = new(engine) | ||
Configuration.new(destination, &block) | ||
destination | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# coding: utf-8 | ||
require 'active_support/concern' | ||
require_relative 'base' | ||
|
||
module RedisCounters | ||
module Dumpers | ||
module Dsl | ||
# Модуль реализующий DSL для класса Engine::Base | ||
module Engine | ||
extend ActiveSupport::Concern | ||
|
||
class Configuration < ::RedisCounters::Dumpers::Dsl::Base | ||
alias_method :engine, :target | ||
|
||
setter :name | ||
setter :fields | ||
setter :temp_table_name | ||
|
||
callback_setter :on_before_merge | ||
callback_setter :on_prepare_row | ||
callback_setter :on_after_merge | ||
callback_setter :on_after_delete | ||
|
||
def destination(&block) | ||
engine.destinations << ::RedisCounters::Dumpers::Destination.build(engine, &block) | ||
end | ||
end | ||
|
||
module ClassMethods | ||
def build(&block) | ||
engine = new | ||
Configuration.new(engine, &block) | ||
engine | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# coding: utf-8 | ||
require 'active_support/concern' | ||
require 'redis_counters/dumpers/engine' | ||
|
||
module RedisCounters | ||
module Dumpers | ||
module Dsl | ||
module List | ||
extend ActiveSupport::Concern | ||
|
||
module ClassMethods | ||
def build(&block) | ||
instance = new | ||
instance.instance_eval(&block) | ||
instance | ||
end | ||
end | ||
|
||
def dumper(id, &block) | ||
engine = ::RedisCounters::Dumpers::Engine.build(&block) | ||
engine.name = id | ||
@dumpers[id] = engine | ||
end | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.