znamenica/dneslov

View on GitHub
app/models/memo.rb

Summary

Maintainability
D
2 days
Test Coverage
F
33%
# frozen_string_literal: true

require 'when_easter'

# class Memo содержит сведения о помине какой-либо памяти в календаре,
# таким образом связывая память и календарь. Может быть либо прямым обычным помином,
# содержащим поле годовой даты year_date, либо соборным помином, таким
# образом указывая на другой помин, содержащий годовую дату и имеющим род собора.
#
# add_date[string]            - дата добавления записи в календарь
# year_date[string]           - дата в году постоянная или перемещаемая, когда память отмечается
# event_id[int]               - ссылка на событие
# calendary_id[int]           - ссылка на календарь
# bind_kind_code[string]      - тип привязки к опорному помину(может быть не привязан)
# bond_to_id[int]             - ссылка на опорный помин, если nil, помин первичный
# id[int]                     - опознаватель
class Memo < ActiveRecord::Base
   extend TotalSize
   include WithDescriptions
   include WithLinks
   include DistinctBy

   DAYS = %w(нд пн вт ср чт пт сб)
   DAYSR = DAYS.dup.reverse
   DAYSN = DAYS.dup.rotate
   CONDITIONALS = {
      '<' => (1..7),
      '>' => (-7..-1),
      '%' => (0..6),
      '~' => (-3..3),
   }

   belongs_to :calendary
   belongs_to :event
   belongs_to :bond_to, class_name: :Memo
   belongs_to :bind_kind, primary_key: :key, foreign_key: :bind_kind_code, class_name: :Subject

   has_one :memo_order
   has_one :order, through: :memo_order
   has_one :memory, through: :event
   has_one :slug, through: :memory
   has_one :thumb, -> { order(Arel.sql("random()")).select(:url).limit(1) }, through: :memory, source: :thumbs
   has_one :calendary_slug, through: :calendary, source: :slug, class_name: :Slug
   has_one :memory_slug, through: :memory, source: :slug, class_name: :Slug

   has_many :memo_orders, dependent: :destroy
   has_many :orders, through: :memo_orders
   has_many :slugs, -> { distinct.reorder('id') }, through: :orders, source: :slug
   has_many :order_titles, through: :orders, source: :tweets
   has_many :bond_memo_titles, through: :bond_to, source: :titles
   has_many :event_titles, through: :event, source: :titles
   has_many :services, as: :info, inverse_of: :info # TODO додаь задачу превода service_links/ServiceLink во Service
   has_many :titles, -> { title }, as: :describable, dependent: :delete_all
   has_many :notes, as: :describable, dependent: :delete_all, class_name: :Note

   delegate :quantity, to: :memory
   delegate :quantity, to: :bond_to, prefix: true, allow_nil: true
   # alias_method orig, new

   scope :primary, -> { where( bond_to_id: nil ) }
   scope :licit, -> { joins( :calendary ).where( calendaries: { licit: true })}
   scope :licit_with, ->(c) { self.left_outer_joins(:slug).licit.or(self.in_calendaries(c)) }
   scope :in_calendaries, -> calendaries_in do
      return self if calendaries_in.blank?

      # TODO make single embedded select or after fix rails bug use merge
      calendaries = calendaries_in.is_a?(String) && calendaries_in.split(',') || calendaries_in
      calendary_ids = Slug.where( text: calendaries, sluggable_type: 'Calendary' ).select( :sluggable_id )
      where( calendary_id: calendary_ids )
   end

   scope :first_in_calendary, -> do
      sql = self.joins(:calendary_slug, :memory)
                .select('row_number() OVER (PARTITION BY slugs.text, memories.id)')
      self.unscope(:where, :order)
          .joins("INNER JOIN (#{sql.to_sql}) AS tmp ON tmp.row_number = 1 AND tmp.id = memoes.id")
   end

   scope :with_key, -> _ do
      join_name = table.table_alias || table.name
      selector = ["#{join_name}.id AS _key"]

      select(selector).group('_key').reorder("_key")
   end

   scope :with_value, -> context do
      language_codes = [context[:locales]].flatten
      alphabeth_codes = Languageble.alphabeth_list_for(language_codes).flatten
      join_name = table.table_alias || table.name
      selector = self.select_values.dup

      join = "LEFT OUTER JOIN descriptions AS titles
                           ON titles.id IS NOT NULL
                          AND titles.describable_id = #{join_name}.id
                          AND titles.describable_type = 'Memo'
                          AND titles.type = 'Title'
                          AND titles.language_code IN ('#{language_codes.join("', '")}')
                          AND titles.alphabeth_code IN ('#{alphabeth_codes.join("', '")}')

              LEFT OUTER JOIN events AS memo_events
                           ON memo_events.id = #{join_name}.event_id
                         JOIN subjects AS event_kinds
                           ON event_kinds.key = memo_events.kind_code
                          AND event_kinds.kind_code = 'EventKind'
                         JOIN descriptions AS event_titles
                           ON event_titles.id IS NOT NULL
                          AND (event_titles.describable_id = memo_events.id
                          AND event_titles.describable_type = 'Event'
                          AND event_titles.type = 'Title'
                           OR event_titles.describable_id = event_kinds.id
                          AND event_titles.describable_type = 'Subject'
                          AND event_titles.type = 'Appellation')
                          AND event_titles.alphabeth_code IN ('#{alphabeth_codes.join("', '")}')
                          AND event_titles.language_code IN ('#{language_codes.join("', '")}')"

      selector << "memoes.year_date || ' [' || memo_events.happened_at || ' - ' || COALESCE(event_titles.text, '') || ' - ' || COALESCE(titles.text, '') || ']' AS _value"

      joins(join).select(selector).group(:id, "titles.text", "memo_events.happened_at", "event_titles.text")
   end

   scope :with_slug_text, -> do
      selector = self.select_values.dup
      if selector.empty?
         selector << 'memoes.*'
      end
      selector << 'slugs.text AS _slug'

      left_outer_joins(:slug).select(selector.uniq).group('_slug').order('_slug')
   end

   scope :by_date, ->(dates_in, julian = false) do
      return self if dates_in.blank?

      result = Memo.dates_to_days(dates_in, julian)

      where(year_date: result)
   end

   scope :by_token, -> text do
      left_outer_joins(:descriptions, :titles, :memory).
         where( "unaccent(descriptions.text) ~* unaccent(?)", "\\m#{text}.*" ).or(
         where( "unaccent(titles_memoes.text) ~* unaccent(?)", "\\m#{text}.*" ).or(
         where( "memories.short_name ~* ?", "\\m#{text}.*" ).or(
         where( "memoes.add_date ~* ?", "\\m#{text}.*" ).or(
         where( "memoes.year_date ~* ?", "\\m#{text}.*" )))))
   end

   scope :by_tokens, -> string_in do
      return self if string_in.blank?
      # TODO fix the correctness of the query
      klass = self.model_name.name.constantize
      or_rel_tokens = string_in.split(/\//).map do |or_token|
         # OR operation
         or_token.strip.split(/\s+/).reduce(nil) do |rel, and_token|
            # AND operation
            and_rel = klass.by_token(and_token)
            rel && rel.merge(and_rel) || and_rel
         end
      end

      or_rel = or_rel_tokens.reduce { |sum_rel, rel| sum_rel.or(rel) }
      self.merge(or_rel).distinct
   end

   scope :by_event_id, -> (event_id) do
      where(event_id: event_id)
   end

   scope :by_calendary_id, -> (calendary_id) do
      where(calendary_id: calendary_id)
   end

   scope :by_memory_id, -> (memory_id) do
      joins(:event).merge(Event.by_memory_id(memory_id))
   end

   scope :notice, -> { joins(:event).merge(Event.notice) }

   singleton_class.send(:alias_method, :t, :by_token)
   singleton_class.send(:alias_method, :q, :by_tokens)
   singleton_class.send(:alias_method, :d, :by_date)
   singleton_class.send(:alias_method, :mid, :by_memory_id)
   singleton_class.send(:alias_method, :c, :in_calendaries)

   scope :with_value_memoried, -> context do 
      language_codes = [context[:locales]].flatten
      alphabeth_codes = Languageble.alphabeth_list_for(language_codes).flatten
      as = table.table_alias || table.name
      selector = self.select_values.dup

      joi = "LEFT OUTER JOIN descriptions AS #{as}_titles
                          ON #{as}_titles.describable_id = #{as}.id
                         AND #{as}_titles.describable_type = 'Memo'
                         AND #{as}_titles.type = 'Title'
                         AND #{as}_titles.language_code IN ('#{language_codes.join("', '")}')
                         AND #{as}_titles.alphabeth_code IN ('#{alphabeth_codes.join("', '")}')
             LEFT OUTER JOIN events AS #{as}_events
                          ON #{as}.event_id = #{as}_events.id
                        JOIN subjects AS #{as}_event_kinds
                          ON #{as}_event_kinds.key = #{as}_events.kind_code
                         AND #{as}_event_kinds.kind_code = 'EventKind'
                        JOIN descriptions AS #{as}_event_titles
                          ON #{as}_event_titles.id IS NOT NULL
                         AND (#{as}_event_titles.describable_id = #{as}_events.id
                         AND #{as}_event_titles.describable_type = 'Event'
                         AND #{as}_event_titles.type = 'Title'
                          OR #{as}_event_titles.describable_id = #{as}_event_kinds.id
                         AND #{as}_event_titles.describable_type = 'Subject'
                         AND #{as}_event_titles.type = 'Appellation')
                         AND #{as}_event_titles.alphabeth_code IN ('#{alphabeth_codes.join("', '")}')
                         AND #{as}_event_titles.language_code IN ('#{language_codes.join("', '")}')
             LEFT OUTER JOIN calendaries AS #{as}_calendaries
                          ON #{as}_calendaries.id = #{as}.calendary_id
                        JOIN descriptions AS #{as}_calendary_titles
                          ON #{as}_calendary_titles.describable_id = #{as}_calendaries.id
                         AND #{as}_calendary_titles.describable_type = 'Calendary'
                         AND #{as}_calendary_titles.type = 'Appellation'
                         AND #{as}_calendary_titles.language_code IN ('#{language_codes.join("', '")}')
                         AND #{as}_calendary_titles.alphabeth_code IN ('#{alphabeth_codes.join("', '")}')
             LEFT OUTER JOIN memories AS #{as}_memories
                          ON #{as}_memories.id = #{as}_events.memory_id"

      selector << "#{as}_memories.short_name || ' [' ||
         COALESCE(#{as}_event_titles.text, '') || ' (' ||
         #{as}_events.happened_at || ')]: ' ||
         COALESCE(#{as}_titles.text, '') || '[' ||
         COALESCE(#{as}_calendary_titles.text, '') || ' (' ||
         memoes.year_date || ')]' AS _value"

      joins(joi).select(selector).group(:id, "#{as}_memories.short_name", "#{as}_events.happened_at", "#{as}_event_titles.text", "#{as}_titles.text", "#{as}_calendary_titles.text", "memoes.year_date")
   end

   scope :with_event, -> do
      selector = 'order_table.order_no AS _event_code'
      list = Event::SORT.map.with_index {|x, i| "('#{x}', #{i})" }
      join = "LEFT OUTER JOIN (VALUES #{list.join(", ")})
              AS order_table (event_kind_code, order_no)
              ON order_table.event_kind_code = events.kind_code"

      joins(:event, join).select(selector).group('_event_code').order('_event_code')
   end

   scope :with_base_year, -> do
      selector = self.select_values.dup
      if selector.empty?
         selector << 'memoes.*'
      end
      selector << 'memories.base_year AS _base_year'

      joins(:memory).select(selector).group('_base_year').order('_base_year')
   end

   scope :with_date, -> do
      selector = 'events.happened_at AS _happened_at'

      joins(:event).select(selector).group('_happened_at')
   end

   scope :with_thumb_url, -> do
      selector = 'thumbs.url AS _thumb_url'

      left_outer_joins(:thumb).select(selector).group('_thumb_url')
#      as = table.table_alias || table.name
#      selector = self.select_values.dup
#      selector << "#{as}.*" if selector.empty?
#      selector << "COALESCE(
#                      SELECT #{as}_thumbs.url AS thumb_url
#                        FROM thumbs AS #{as}_thumbs
#                       WHERE #{as}_thumbs.thumbable_id = #{as}.id
#                         AND #{as}_thumbs.thumbable_type = 'Memory'
#                    ORDER BY random()
#                       LIMIT 1) AS _thumb_url"
#
#      select(selector).group(:id)
   end

   scope :with_bond_to_title, -> context do
      join_name = table.table_alias || table.name
      language_codes = [context[:locales]].flatten
      alphabeth_codes = Languageble.alphabeth_list_for(language_codes).flatten
      /`(?<scope_name>[^']*)'/ =~ caller.grep(/delegation/)[1]

      selector = "descriptions_#{scope_name}.text AS _bond_to_title"
      join =
         "LEFT OUTER JOIN memoes
                       AS memoes_#{scope_name}
                       ON memoes_#{scope_name}.id = #{join_name}.bond_to_id
          LEFT OUTER JOIN descriptions
                       AS descriptions_#{scope_name}
                       ON descriptions_#{scope_name}.describable_type = '#{model}'
                      AND descriptions_#{scope_name}.describable_id = memoes_#{scope_name}.id
                      AND descriptions_#{scope_name}.type = 'Title'
                      AND descriptions_#{scope_name}.alphabeth_code IN ('#{alphabeth_codes.join("', '")}')
                      AND descriptions_#{scope_name}.language_code IN ('#{language_codes.join("', '")}')"

      joins(join).select(selector).group('_bond_to_title')
   end

   scope :with_event_title, -> context do
      join_name = table.table_alias || table.name
      language_codes = [context[:locales]].flatten
      alphabeth_codes = Languageble.alphabeth_list_for(language_codes).flatten
      /`(?<scope_name>[^']*)'/ =~ caller.grep(/delegation/)[1]

      selector = "event_titles_#{scope_name}.text AS _event_title"
      join =
         "    LEFT OUTER JOIN events AS events_#{scope_name}
                           ON events_#{scope_name}.id = #{join_name}.event_id
              LEFT OUTER JOIN subjects AS event_kinds_#{scope_name}
                           ON event_kinds_#{scope_name}.key = events_#{scope_name}.kind_code
                          AND event_kinds_#{scope_name}.kind_code = 'EventKind'
              LEFT OUTER JOIN descriptions AS event_titles_#{scope_name}
                           ON event_titles_#{scope_name}.id IS NOT NULL
                          AND (event_titles_#{scope_name}.describable_id = events_#{scope_name}.id
                          AND event_titles_#{scope_name}.describable_type = 'Event'
                          AND event_titles_#{scope_name}.type = 'Title'
                           OR event_titles_#{scope_name}.describable_id = event_kinds_#{scope_name}.id
                          AND event_titles_#{scope_name}.describable_type = 'Subject'
                          AND event_titles_#{scope_name}.type = 'Appellation')
                          AND event_titles_#{scope_name}.alphabeth_code IN ('#{alphabeth_codes.join("', '")}')
                          AND event_titles_#{scope_name}.language_code IN ('#{language_codes.join("', '")}')"

      joins(join).select(selector).group('_event_title')
   end

   scope :with_calendary_slug_text, ->(context) do
      selector = 'calendary_slugs_memoes.text AS _calendary_slug'

      left_outer_joins(:calendary_slug).select(selector).group('_calendary_slug')
   end

   scope :with_orders, -> context do
      join_name = table.table_alias || table.name
      language_codes = [context[:locales]].flatten
      alphabeth_codes = Languageble.alphabeth_list_for(language_codes).flatten
      /`(?<scope_name>[^']*)'/ =~ caller.grep(/delegation/)[1]

      selector =
         "COALESCE(jsonb_object_agg(
          DISTINCT COALESCE(slugs_#{scope_name}.text, ''), titles_#{scope_name}.text)
            FILTER (
             WHERE slugs_#{scope_name}.id IS NOT NULL
               AND titles_#{scope_name}.id IS NOT NULL), '{}')
                AS _orders"
      join =
         "LEFT OUTER JOIN memo_orders AS memo_orders_#{scope_name}
                       ON memo_orders_#{scope_name}.memo_id = #{join_name}.id
          LEFT OUTER JOIN orders AS orders_#{scope_name}
                       ON orders_#{scope_name}.id = memo_orders_#{scope_name}.order_id
          LEFT OUTER JOIN slugs AS slugs_#{scope_name}
                       ON slugs_#{scope_name}.sluggable_type = 'Order'
                      AND slugs_#{scope_name}.sluggable_id = orders_#{scope_name}.id
          LEFT OUTER JOIN descriptions AS titles_#{scope_name}
                       ON titles_#{scope_name}.describable_type = 'Order'
                      AND titles_#{scope_name}.describable_id = orders_#{scope_name}.id
                      AND titles_#{scope_name}.type = 'Tweet'
                      AND titles_#{scope_name}.alphabeth_code IN ('#{alphabeth_codes.join("', '")}')
                      AND titles_#{scope_name}.language_code IN ('#{language_codes.join("', '")}')"

      joins(join).select(selector)
   end

   scope :with_slug, -> do
      selector = self.select_values.dup
      if selector.empty?
         selector << 'memoes.*'
      end
      selector << 'slugs.text AS _slug'

      left_outer_joins(:slug).select(selector.uniq).group('_slug').order('_slug')
   end

   scope :with_title, -> context do
      join_name = table.table_alias || table.name
      language_codes = [context[:locales]].flatten
      alphabeth_codes = Languageble.alphabeth_list_for(language_codes).flatten
      /`(?<scope_name>[^']*)'/ =~ caller.grep(/delegation/)[1]

      selector = self.select_values.dup | ["#{join_name}.*", "descriptions_#{scope_name}.text AS _title"]
      join =
         "LEFT OUTER JOIN descriptions AS descriptions_#{scope_name}
                       ON descriptions_#{scope_name}.describable_id = memoes.id
                      AND descriptions_#{scope_name}.describable_type = '#{model}'
                      AND descriptions_#{scope_name}.type = 'Title'
                      AND descriptions_#{scope_name}.alphabeth_code IN ('#{alphabeth_codes.join("', '")}')
                      AND descriptions_#{scope_name}.language_code IN ('#{language_codes.join("', '")}')"

      joins(join).select(selector.uniq).group('_title')
   end

   scope :with_bond_to_year_date, -> context do
      language_codes = [ context[:locales] ].flatten
      alphabeth_codes = Languageble.alphabeth_list_for(language_codes).flatten
      selector = self.select_values.dup
      if selector.empty?
         selector << 'memoes.*'
      end

      join = "LEFT OUTER JOIN memoes AS bond_to_memoes
                           ON memoes.bond_to_id = bond_to_memoes.id

              LEFT OUTER JOIN descriptions AS titles
                           ON titles.id IS NOT NULL
                          AND titles.describable_id = bond_to_memoes.id
                          AND titles.describable_type = 'Memo'
                          AND titles.type = 'Title'
                          AND titles.language_code IN ('#{language_codes.join("', '")}')
                          AND titles.alphabeth_code IN ('#{alphabeth_codes.join("', '")}')

              LEFT OUTER JOIN events AS bond_to_memo_events
                           ON bond_to_memo_events.id = bond_to_memoes.event_id
              LEFT OUTER JOIN subjects AS bond_to_event_kinds
                           ON bond_to_event_kinds.key = bond_to_memo_events.kind_code
                          AND bond_to_event_kinds.kind_code = 'EventKind'
              LEFT OUTER JOIN descriptions AS bond_to_event_titles
                           ON bond_to_event_titles.id IS NOT NULL
                          AND (bond_to_event_titles.describable_id = bond_to_memo_events.id
                          AND bond_to_event_titles.describable_type = 'Event'
                          AND bond_to_event_titles.type = 'Title'
                           OR bond_to_event_titles.describable_id = bond_to_event_kinds.id
                          AND bond_to_event_titles.describable_type = 'Subject'
                          AND bond_to_event_titles.type = 'Appellation')
                          AND bond_to_event_titles.alphabeth_code IN ('#{alphabeth_codes.join("', '")}')
                          AND bond_to_event_titles.language_code IN ('#{language_codes.join("', '")}')"

      selector << "                    bond_to_memoes.year_date ||
                                                           ' [' ||
                  COALESCE(bond_to_memo_events.happened_at, '') ||
                                                          ' - ' ||
                        COALESCE(bond_to_event_titles.text, '') ||
                                                          ' - ' ||
                                      COALESCE(titles.text, '') ||
                  ']' AS _bond_to_year_date"

      joins(join).select(selector).group(:id,
                                         "bond_to_memoes.year_date",
                                         "titles.text",
                                         "bond_to_memo_events.happened_at",
                                         "bond_to_event_titles.text")
   end

   scope :with_memo_orders, -> context do
      language_codes = [ context[:locales] ].flatten
      this = table.table_alias || table.name
      /`(?<as>[^']*)'/ =~ caller.grep(/delegation/)[1]
      selector = self.select_values.dup
      selector << "#{this}.*" if selector.empty?

      selector << "COALESCE((WITH __memo_orders AS (
                      SELECT DISTINCT ON(memo_orders.updated_at, memo_orders.order_id)
                             memo_orders.id AS id,
                             memo_orders.order_id AS order_id,
                             memo_order_titles.text AS order
                        FROM memo_orders
             LEFT OUTER JOIN orders
                          ON orders.id = memo_orders.order_id
                        JOIN descriptions AS memo_order_titles
                          ON memo_order_titles.describable_id = orders.id
                         AND memo_order_titles.describable_type = 'Order'
                         AND memo_order_titles.type = 'Note'
                         AND memo_order_titles.language_code IN ('#{language_codes.join("', '")}')
                       WHERE memoes.id = memo_orders.memo_id
                    GROUP BY memo_orders.id, memo_order_titles.text
                    ORDER BY memo_orders.updated_at, memo_orders.order_id)
                      SELECT jsonb_agg(__memo_orders)
                        FROM __memo_orders), '[]'::jsonb) AS _memo_orders"

      select(selector).group(:id)
   end

   scope :with_calendary_title, -> context do
      language_codes = [ context[:locales] ].flatten
      selector = self.select_values.dup
      if selector.empty?
         selector << 'memoes.*'
      end
      selector << 'calendary_titles.text AS _calendary_title'

      join = "LEFT OUTER JOIN calendaries
                           ON calendaries.id = memoes.calendary_id
              LEFT OUTER JOIN descriptions AS calendary_titles
                           ON calendary_titles.describable_id = calendaries.id
                          AND calendary_titles.describable_type = 'Calendary'
                          AND calendary_titles.type = 'Appellation'
                          AND calendary_titles.language_code IN ('#{language_codes.join("', '")}')"

      joins(join).select(selector).group(:id, 'calendary_titles.text')
   end

   scope :with_memory_event, -> context do
      language_codes = [ context[:locales] ].flatten
      selector = self.select_values.dup
      if selector.empty?
         selector << 'memoes.*'
      end
      [
        "memo_event_memories.id AS _memory_id",
        "memo_event_memories.short_name AS _memory_name",
        "event_kind_titles.text || ' (' || memo_events.happened_at || ')' AS _event_short_title"
      ].reduce(selector) { |s, v| s << v }

      join = "LEFT OUTER JOIN events AS memo_events
                           ON memoes.event_id = memo_events.id
                         JOIN memories AS memo_event_memories
                           ON memo_events.memory_id = memo_event_memories.id
              LEFT OUTER JOIN subjects AS event_kinds
                           ON event_kinds.kind_code = 'EventKind'
                          AND event_kinds.key = memo_events.kind_code
              LEFT OUTER JOIN descriptions AS event_kind_titles
                           ON event_kind_titles.describable_id = event_kinds.id
                          AND event_kind_titles.describable_type = 'Subject'
                          AND event_kind_titles.type = 'Appellation'
                          AND event_kind_titles.language_code IN ('#{language_codes.join("', '")}')"

      joins(join).select(selector).group(:id, 'memo_event_memories.id',
                                              'memo_events.happened_at',
                                              'event_kind_titles.text')
   end

   accepts_nested_attributes_for :services, reject_if: :all_blank, allow_destroy: true
   accepts_nested_attributes_for :titles, reject_if: :all_blank, allow_destroy: true
   accepts_nested_attributes_for :notes, reject_if: :all_blank, allow_destroy: true
   accepts_nested_attributes_for :memo_orders, reject_if: :all_blank, allow_destroy: true

   validates_presence_of :calendary, :event
   validates_presence_of :year_date, unless: :bond_to_quantity
   validates_absence_of :year_date, if: :bond_to_quantity
   validates :year_date, format: { with: /\A((0[1-9]|[1-2][0-9]|3[0-1])\.(0[1-9]|1[0-2])([%<>~][0-6])?|[+-]\d{1,3})\z/ }, if: :year_date
   validate :bind_kind_code_present, :same_calendaries, :same_memories, if: :bond_to_id

   before_validation :fix_year_date
   before_create -> { self.bind_kind_code ||= 'несвязаный' }

   class << self
      def dates_to_days dates_in, julian
         [dates_in].flatten.map do |date_in|
            date = date_in.is_a?(Date) && date_in || Date.parse(date_in)
            new_date = date.strftime('%2d.%m')
            gap = julian && 13.days || 0
            wday = (date + gap).wday
            easter = WhenEaster::EasterCalendar.find_greek_easter_date(date.year) - gap

            relays =
               CONDITIONALS.map do |(cond, range)|
                  range.map { |x| (date - x.days).strftime('%2d.%m') + "#{cond}#{wday}" }
               end.flatten

            if !date.leap?
               relays |= relays.grep(/28\.02[%<>~]?/).map do |yeardate|
                  match = yeardate.match(/([%<>~])/)
                  match.present? && "29.02#{match[1]}#{wday}" || "29.02"
               end
            end

            [relays, new_date, format('%+i', date.to_time.yday - easter.yday)]
         end.flatten.uniq
      end
   end

   def year_date_for year
      case year_date
      when /(?<sign>[+-])(?<indent>.*)/
         mul = sign == '-' && -1 || 1
         WhenEaster::EasterCalendar.find_greek_easter_date(year.to_i) + (mul * indent.to_i).days
      when /(?<year_m>.*)%(?<date_m>.*)/
         date = Date.parse([year_m, year].join('.'))
         gap_in = date_m.to_i - date.wday
         gap = gap_in.negative? && gap_in + 7 || gap_in

         date + gap.days
      else
         Date.parse([year_date, year].join('.'))
      end
   rescue StandardError
      Time.at(0)
   end

   def same_calendaries
      if bond_to.calendary_id != calendary_id
         errors.add(:bond_to_id, "'s calendary must be the same as self calendary")
      end
   end

   def same_memories
      if bond_to.memory.id != memory.id
         errors.add(:bond_to_id, "'s memory must be the same as self memory")
      end
   end

   def bind_kind_code_present
      if bind_kind_code.blank?
         errors.add(:bind_kind_code, 'must be present when memo is bond to')
      end
   end

   def fix_year_date
      self.year_date =
      case self.year_date
      when /пасха/
         "+0"
      when /^дн\.(\d+)\.по пасхе/ #+24
         "+#{$1}"
      when /^дн\.(\d+)\.до пасхи/ #-24
         "-#{$1}"
      when /^(#{DAYS.join("|")})\.(\d+)\.по пасхе/ #+70
         daynum = DAYSN.index($1) + 1
         days = ($2.to_i - 1) * 7 + daynum
         "+#{days}"
      when /^(#{DAYS.join("|")})\.по пасхе/    #+7
         daynum = DAYSN.index($1) + 1
         "+#{daynum}"
      when /^(#{DAYS.join("|")})\.(\d+)\.до пасхи/  #-14
         daynum = DAYSR.index($1) + 1
         days = ($2.to_i - 1) * 7 + daynum
         "-#{days}"
      when /^(#{DAYS.join("|")})\.до пасхи/    #-7
         daynum = DAYSR.index($1) + 1
         "-#{daynum}"
      when /^дн\.(\d+)\.с (\d+\.\d+)$/   #29.06%7
         daynum = DAYS.index($1)
         date = Time.parse("#{$2}.1970")
         "#{date.strftime("%1d.%m")}%#{daynum}"
      when /^дн\.(\d+)\.по (\d+\.\d+)$/   #29.06<7
         date = Time.parse("#{$2}.1970") + $1.to_i
         date.strftime("%1d.%m")
      when /^(#{DAYS.join("|")})\.близ (\d+\.\d+)$/   #29.06~7
         daynum = DAYS.index($1)
         date = Time.parse("#{$2}.1970") # - 4.days
         "#{date.strftime("%1d.%m")}~#{daynum}"
      when /^(#{DAYS.join("|")})\.по (\d+\.\d+)$/   #29.06<7
         daynum = DAYS.index($1)
         date = Time.parse("#{$2}.1970")
         "#{date.strftime("%1d.%m")}<#{daynum}"
      when /^(#{DAYS.join("|")})\.до (\d+\.\d+)$/  #21.06>7
         daynum = DAYS.index($1)
         date = Time.parse("#{$2}.1970")# - 8.days
         "#{date.strftime("%1d.%m")}>#{daynum}"
      when /^(#{DAYS.join("|")})\.(\d+)\.по (\d+\.\d+)$/   #29.06<7
         daynum = DAYS.index($1)
         date = Time.parse("#{$3}.1970") + ($2.to_i - 1) * 7
         "#{date.strftime("%1d.%m")}<#{daynum}"
      when /^(#{DAYS.join("|")})\.(\d+)\.до (\d+\.\d+)$/  #21.06>7
         daynum = DAYS.index($1)
         date = Time.parse("#{$3}.1970") - 8.days - ($2.to_i - 1) * 7
         "#{date.strftime("%1d.%m")}>#{daynum}"
      when /^$/
         nil
      else
         self.year_date
      end
   end
end