zermelo 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +10 -0
 - data/README.md +76 -52
 - data/lib/zermelo/associations/association_data.rb +4 -3
 - data/lib/zermelo/associations/class_methods.rb +37 -50
 - data/lib/zermelo/associations/index.rb +3 -1
 - data/lib/zermelo/associations/multiple.rb +247 -0
 - data/lib/zermelo/associations/range_index.rb +44 -0
 - data/lib/zermelo/associations/singular.rb +193 -0
 - data/lib/zermelo/associations/unique_index.rb +4 -3
 - data/lib/zermelo/backend.rb +120 -0
 - data/lib/zermelo/backends/{influxdb_backend.rb → influxdb.rb} +87 -31
 - data/lib/zermelo/backends/{redis_backend.rb → redis.rb} +53 -58
 - data/lib/zermelo/backends/stub.rb +43 -0
 - data/lib/zermelo/filter.rb +194 -0
 - data/lib/zermelo/filters/index_range.rb +22 -0
 - data/lib/zermelo/filters/{influxdb_filter.rb → influxdb.rb} +12 -11
 - data/lib/zermelo/filters/redis.rb +173 -0
 - data/lib/zermelo/filters/steps/list_step.rb +48 -30
 - data/lib/zermelo/filters/steps/set_step.rb +148 -89
 - data/lib/zermelo/filters/steps/sort_step.rb +2 -2
 - data/lib/zermelo/record.rb +53 -0
 - data/lib/zermelo/records/attributes.rb +32 -0
 - data/lib/zermelo/records/class_methods.rb +12 -25
 - data/lib/zermelo/records/{influxdb_record.rb → influxdb.rb} +3 -4
 - data/lib/zermelo/records/instance_methods.rb +9 -8
 - data/lib/zermelo/records/key.rb +3 -1
 - data/lib/zermelo/records/redis.rb +17 -0
 - data/lib/zermelo/records/stub.rb +17 -0
 - data/lib/zermelo/version.rb +1 -1
 - data/spec/lib/zermelo/associations/index_spec.rb +70 -1
 - data/spec/lib/zermelo/associations/multiple_spec.rb +1084 -0
 - data/spec/lib/zermelo/associations/range_index_spec.rb +77 -0
 - data/spec/lib/zermelo/associations/singular_spec.rb +149 -0
 - data/spec/lib/zermelo/associations/unique_index_spec.rb +58 -2
 - data/spec/lib/zermelo/filter_spec.rb +363 -0
 - data/spec/lib/zermelo/locks/redis_lock_spec.rb +3 -3
 - data/spec/lib/zermelo/records/instance_methods_spec.rb +206 -0
 - data/spec/spec_helper.rb +9 -1
 - data/spec/support/mock_logger.rb +48 -0
 - metadata +31 -46
 - data/lib/zermelo/associations/belongs_to.rb +0 -115
 - data/lib/zermelo/associations/has_and_belongs_to_many.rb +0 -128
 - data/lib/zermelo/associations/has_many.rb +0 -120
 - data/lib/zermelo/associations/has_one.rb +0 -109
 - data/lib/zermelo/associations/has_sorted_set.rb +0 -124
 - data/lib/zermelo/backends/base.rb +0 -115
 - data/lib/zermelo/filters/base.rb +0 -212
 - data/lib/zermelo/filters/redis_filter.rb +0 -111
 - data/lib/zermelo/filters/steps/sorted_set_step.rb +0 -156
 - data/lib/zermelo/records/base.rb +0 -62
 - data/lib/zermelo/records/redis_record.rb +0 -27
 - data/spec/lib/zermelo/associations/belongs_to_spec.rb +0 -6
 - data/spec/lib/zermelo/associations/has_many_spec.rb +0 -6
 - data/spec/lib/zermelo/associations/has_one_spec.rb +0 -6
 - data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +0 -6
 - data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
 - data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
 - data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
 - data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
 - data/spec/lib/zermelo/records/influxdb_record_spec.rb +0 -434
 - data/spec/lib/zermelo/records/key_spec.rb +0 -6
 - data/spec/lib/zermelo/records/redis_record_spec.rb +0 -1461
 - data/spec/lib/zermelo/records/type_validator_spec.rb +0 -6
 - data/spec/lib/zermelo/version_spec.rb +0 -6
 - data/spec/lib/zermelo_spec.rb +0 -6
 
| 
         @@ -0,0 +1,247 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'forwardable'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Zermelo
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Associations
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Multiple
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  extend Forwardable
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def_delegators :filter, :intersect, :union, :diff, :sort,
         
     | 
| 
      
 10 
     | 
    
         
            +
                                   :find_by_id, :find_by_ids, :find_by_id!, :find_by_ids!,
         
     | 
| 
      
 11 
     | 
    
         
            +
                                   :page, :all, :each, :collect, :map,
         
     | 
| 
      
 12 
     | 
    
         
            +
                                   :select, :find_all, :reject, :destroy_all,
         
     | 
| 
      
 13 
     | 
    
         
            +
                                   :ids, :count, :empty?, :exists?,
         
     | 
| 
      
 14 
     | 
    
         
            +
                                   :associated_ids_for
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  def initialize(type, parent_klass, parent_id, name)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @type         = type
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @parent_klass = parent_klass
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @parent_id    = parent_id
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @name         = name
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    @backend      = parent_klass.send(:backend)
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    @key_type = case @type
         
     | 
| 
      
 25 
     | 
    
         
            +
                    when :has_many, :has_and_belongs_to_many
         
     | 
| 
      
 26 
     | 
    
         
            +
                      :set
         
     | 
| 
      
 27 
     | 
    
         
            +
                    when :has_sorted_set
         
     | 
| 
      
 28 
     | 
    
         
            +
                      :sorted_set
         
     | 
| 
      
 29 
     | 
    
         
            +
                    end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                    @record_ids_key = Zermelo::Records::Key.new(
         
     | 
| 
      
 32 
     | 
    
         
            +
                      :klass  => parent_klass,
         
     | 
| 
      
 33 
     | 
    
         
            +
                      :id     => parent_id,
         
     | 
| 
      
 34 
     | 
    
         
            +
                      :name   => "#{name}_ids",
         
     | 
| 
      
 35 
     | 
    
         
            +
                      :type   => @key_type,
         
     | 
| 
      
 36 
     | 
    
         
            +
                      :object => :association
         
     | 
| 
      
 37 
     | 
    
         
            +
                    )
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    parent_klass.send(:with_association_data, name.to_sym) do |data|
         
     | 
| 
      
 40 
     | 
    
         
            +
                      @associated_class = data.data_klass
         
     | 
| 
      
 41 
     | 
    
         
            +
                      @lock_klasses     = [data.data_klass] + data.related_klasses
         
     | 
| 
      
 42 
     | 
    
         
            +
                      @inverse          = data.inverse
         
     | 
| 
      
 43 
     | 
    
         
            +
                      @sort_key         = data.sort_key
         
     | 
| 
      
 44 
     | 
    
         
            +
                      @sort_order       = data.sort_order
         
     | 
| 
      
 45 
     | 
    
         
            +
                      @callbacks        = data.callbacks
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    raise ':inverse_of must be set' if @inverse.nil?
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  def first
         
     | 
| 
      
 52 
     | 
    
         
            +
                    # FIXME raise error unless :has_sorted_set.eql?(@type)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    filter.first
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  def last
         
     | 
| 
      
 57 
     | 
    
         
            +
                    # FIXME raise error unless :has_sorted_set.eql?(@type)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    filter.last
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  def <<(record)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    add(record)
         
     | 
| 
      
 63 
     | 
    
         
            +
                    self  # for << 'a' << 'b'
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                  def add(*records)
         
     | 
| 
      
 67 
     | 
    
         
            +
                    raise 'No records to add' if records.empty?
         
     | 
| 
      
 68 
     | 
    
         
            +
                    raise 'Invalid record class' if records.any? {|r| !r.is_a?(@associated_class)}
         
     | 
| 
      
 69 
     | 
    
         
            +
                    raise 'Record(s) must have been saved' unless records.all? {|r| r.persisted?} # may need to be moved
         
     | 
| 
      
 70 
     | 
    
         
            +
                    @parent_klass.lock(*@lock_klasses) do
         
     | 
| 
      
 71 
     | 
    
         
            +
                      record_ids = case @type
         
     | 
| 
      
 72 
     | 
    
         
            +
                      when :has_many, :has_and_belongs_to_many
         
     | 
| 
      
 73 
     | 
    
         
            +
                        records.is_a?(Zermelo::Filter) ? records.ids : records.map(&:id)
         
     | 
| 
      
 74 
     | 
    
         
            +
                      when :has_sorted_set
         
     | 
| 
      
 75 
     | 
    
         
            +
                        records.map {|r| [r.send(@sort_key.to_sym).to_f, r.id]}
         
     | 
| 
      
 76 
     | 
    
         
            +
                      end
         
     | 
| 
      
 77 
     | 
    
         
            +
                      _add_ids({:callbacks => true}, *record_ids)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    end
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  def add_ids(*record_ids)
         
     | 
| 
      
 82 
     | 
    
         
            +
                    raise 'No record ids to add' if record_ids.empty?
         
     | 
| 
      
 83 
     | 
    
         
            +
                    @parent_klass.lock(*@lock_klasses) do
         
     | 
| 
      
 84 
     | 
    
         
            +
                      records = @associated_class.find_by_ids!(*record_ids)
         
     | 
| 
      
 85 
     | 
    
         
            +
                      _add_ids({:callbacks => true}, *record_ids)
         
     | 
| 
      
 86 
     | 
    
         
            +
                    end
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                  # TODO support dependent delete, for now just removes the association
         
     | 
| 
      
 90 
     | 
    
         
            +
                  def remove(*records)
         
     | 
| 
      
 91 
     | 
    
         
            +
                    raise 'No records to remove' if records.empty?
         
     | 
| 
      
 92 
     | 
    
         
            +
                    raise 'Invalid record class' if records.any? {|r| !r.is_a?(@associated_class)}
         
     | 
| 
      
 93 
     | 
    
         
            +
                    raise 'Record(s) must have been saved' unless records.all? {|r| r.persisted?} # may need to be moved
         
     | 
| 
      
 94 
     | 
    
         
            +
                    @parent_klass.lock(*@lock_klasses) do
         
     | 
| 
      
 95 
     | 
    
         
            +
                      _remove_ids({:callbacks => true},
         
     | 
| 
      
 96 
     | 
    
         
            +
                        *(records.is_a?(Zermelo::Filter) ? records.ids : records.map(&:id)))
         
     | 
| 
      
 97 
     | 
    
         
            +
                    end
         
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                  def remove_ids(*record_ids)
         
     | 
| 
      
 101 
     | 
    
         
            +
                    raise 'No record ids to remove' if record_ids.empty?
         
     | 
| 
      
 102 
     | 
    
         
            +
                    @parent_klass.lock(*@lock_klasses) do
         
     | 
| 
      
 103 
     | 
    
         
            +
                      _remove_ids({:callbacks => true}, *record_ids)
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                  def clear
         
     | 
| 
      
 108 
     | 
    
         
            +
                    @parent_klass.lock(*@lock_klasses) do
         
     | 
| 
      
 109 
     | 
    
         
            +
                      _remove_ids({:callbacks => true}, *filter.ids) unless filter.empty?
         
     | 
| 
      
 110 
     | 
    
         
            +
                    end
         
     | 
| 
      
 111 
     | 
    
         
            +
                  end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                  private
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                  def _inverse
         
     | 
| 
      
 116 
     | 
    
         
            +
                    return @inverse_obj unless @inverse_obj.nil?
         
     | 
| 
      
 117 
     | 
    
         
            +
                    @associated_class.send(:with_association_data, @inverse.to_sym) do |data|
         
     | 
| 
      
 118 
     | 
    
         
            +
                      @inverse_obj = case @type
         
     | 
| 
      
 119 
     | 
    
         
            +
                      when :has_many, :has_sorted_set
         
     | 
| 
      
 120 
     | 
    
         
            +
                        # inverse is belongs_to
         
     | 
| 
      
 121 
     | 
    
         
            +
                        # FIXME neater to do multiple hash keys at once, if backends support it
         
     | 
| 
      
 122 
     | 
    
         
            +
                        Zermelo::Records::Key.new(
         
     | 
| 
      
 123 
     | 
    
         
            +
                          :klass  => @associated_class,
         
     | 
| 
      
 124 
     | 
    
         
            +
                          :name   => 'belongs_to',
         
     | 
| 
      
 125 
     | 
    
         
            +
                          :type   => :hash,
         
     | 
| 
      
 126 
     | 
    
         
            +
                          :object => :association
         
     | 
| 
      
 127 
     | 
    
         
            +
                        )
         
     | 
| 
      
 128 
     | 
    
         
            +
                      when :has_and_belongs_to_many
         
     | 
| 
      
 129 
     | 
    
         
            +
                        Zermelo::Records::Key.new(
         
     | 
| 
      
 130 
     | 
    
         
            +
                          :klass  => @associated_class,
         
     | 
| 
      
 131 
     | 
    
         
            +
                          :name   => "#{@inverse}_ids",
         
     | 
| 
      
 132 
     | 
    
         
            +
                          :type   => :set,
         
     | 
| 
      
 133 
     | 
    
         
            +
                          :object => :association
         
     | 
| 
      
 134 
     | 
    
         
            +
                        )
         
     | 
| 
      
 135 
     | 
    
         
            +
                      end
         
     | 
| 
      
 136 
     | 
    
         
            +
                    end
         
     | 
| 
      
 137 
     | 
    
         
            +
                    @inverse_obj
         
     | 
| 
      
 138 
     | 
    
         
            +
                  end
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                  # associated will be a belongs_to; on remove already runs inside a lock and transaction
         
     | 
| 
      
 141 
     | 
    
         
            +
                  def on_remove
         
     | 
| 
      
 142 
     | 
    
         
            +
                    _remove_ids({:callbacks => false}, *filter.ids) unless filter.empty?
         
     | 
| 
      
 143 
     | 
    
         
            +
                  end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                  def _add_ids(opts = {}, *record_ids)
         
     | 
| 
      
 146 
     | 
    
         
            +
                    ba = @callbacks[:before_add]
         
     | 
| 
      
 147 
     | 
    
         
            +
                    if ba.nil? || !opts[:callbacks] || !@parent_klass.respond_to?(ba) ||
         
     | 
| 
      
 148 
     | 
    
         
            +
                      !@parent_klass.send(ba, @parent_id, *record_ids).is_a?(FalseClass)
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
                      new_txn = @backend.begin_transaction
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                      # FIXME neater to do multiple hash keys at once for inverse, if backends support it
         
     | 
| 
      
 153 
     | 
    
         
            +
                      case @type
         
     | 
| 
      
 154 
     | 
    
         
            +
                      when :has_many
         
     | 
| 
      
 155 
     | 
    
         
            +
                        # inverse is belongs_to
         
     | 
| 
      
 156 
     | 
    
         
            +
                        record_ids.each do |record_id|
         
     | 
| 
      
 157 
     | 
    
         
            +
                          _inverse.id = record_id
         
     | 
| 
      
 158 
     | 
    
         
            +
                          @backend.add(_inverse, "#{@inverse}_id" => @parent_id)
         
     | 
| 
      
 159 
     | 
    
         
            +
                        end
         
     | 
| 
      
 160 
     | 
    
         
            +
                      when :has_sorted_set
         
     | 
| 
      
 161 
     | 
    
         
            +
                        # inverse is belongs_to
         
     | 
| 
      
 162 
     | 
    
         
            +
                        record_ids.each do |(score, record_id)|
         
     | 
| 
      
 163 
     | 
    
         
            +
                          _inverse.id = record_id
         
     | 
| 
      
 164 
     | 
    
         
            +
                          @backend.add(_inverse, "#{@inverse}_id" => @parent_id)
         
     | 
| 
      
 165 
     | 
    
         
            +
                        end
         
     | 
| 
      
 166 
     | 
    
         
            +
                      when :has_and_belongs_to_many
         
     | 
| 
      
 167 
     | 
    
         
            +
                        # inverse is has_and_belongs_to_many
         
     | 
| 
      
 168 
     | 
    
         
            +
                        record_ids.each do |record_id|
         
     | 
| 
      
 169 
     | 
    
         
            +
                          _inverse.id = record_id
         
     | 
| 
      
 170 
     | 
    
         
            +
                          @backend.add(_inverse, @parent_id)
         
     | 
| 
      
 171 
     | 
    
         
            +
                        end
         
     | 
| 
      
 172 
     | 
    
         
            +
                      end
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                      @backend.add(@record_ids_key, record_ids)
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                      @backend.commit_transaction if new_txn
         
     | 
| 
      
 177 
     | 
    
         
            +
                      aa = @callbacks[:after_add]
         
     | 
| 
      
 178 
     | 
    
         
            +
                      if !aa.nil? && opts[:callbacks] && @parent_klass.respond_to?(aa)
         
     | 
| 
      
 179 
     | 
    
         
            +
                        @parent_klass.send(aa, @parent_id, *record_ids)
         
     | 
| 
      
 180 
     | 
    
         
            +
                      end
         
     | 
| 
      
 181 
     | 
    
         
            +
                    end
         
     | 
| 
      
 182 
     | 
    
         
            +
                  end
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                  def _remove_ids(opts = {}, *record_ids)
         
     | 
| 
      
 185 
     | 
    
         
            +
                    br = @callbacks[:before_remove]
         
     | 
| 
      
 186 
     | 
    
         
            +
                    if br.nil? || !opts[:callbacks] || !@parent_klass.respond_to?(br) ||
         
     | 
| 
      
 187 
     | 
    
         
            +
                      !@parent_klass.send(br, @parent_id, *record_ids).is_a?(FalseClass)
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
                      new_txn = @backend.begin_transaction
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                      # FIXME neater to do multiple hash keys at once for inverse, if backends support it
         
     | 
| 
      
 192 
     | 
    
         
            +
                      case @type
         
     | 
| 
      
 193 
     | 
    
         
            +
                      when :has_many, :has_sorted_set
         
     | 
| 
      
 194 
     | 
    
         
            +
                        # inverse is belongs_to
         
     | 
| 
      
 195 
     | 
    
         
            +
                        record_ids.each do |record_id|
         
     | 
| 
      
 196 
     | 
    
         
            +
                          _inverse.id = record_id
         
     | 
| 
      
 197 
     | 
    
         
            +
                          @backend.delete(_inverse, "#{@inverse}_id")
         
     | 
| 
      
 198 
     | 
    
         
            +
                        end
         
     | 
| 
      
 199 
     | 
    
         
            +
                      when :has_and_belongs_to_many
         
     | 
| 
      
 200 
     | 
    
         
            +
                        # inverse is has_and_belongs_to_many
         
     | 
| 
      
 201 
     | 
    
         
            +
                        record_ids.each do |record_id|
         
     | 
| 
      
 202 
     | 
    
         
            +
                          _inverse.id = record_id
         
     | 
| 
      
 203 
     | 
    
         
            +
                          @backend.delete(_inverse, @parent_id)
         
     | 
| 
      
 204 
     | 
    
         
            +
                        end
         
     | 
| 
      
 205 
     | 
    
         
            +
                      end
         
     | 
| 
      
 206 
     | 
    
         
            +
             
     | 
| 
      
 207 
     | 
    
         
            +
                      @backend.delete(@record_ids_key, record_ids)
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
                      @backend.commit_transaction if new_txn
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                      ar = @callbacks[:after_remove]
         
     | 
| 
      
 212 
     | 
    
         
            +
                      if !ar.nil? && opts[:callbacks] && @parent_klass.respond_to?(ar)
         
     | 
| 
      
 213 
     | 
    
         
            +
                        @parent_klass.send(ar, @parent_id, *record_ids)
         
     | 
| 
      
 214 
     | 
    
         
            +
                      end
         
     | 
| 
      
 215 
     | 
    
         
            +
                    end
         
     | 
| 
      
 216 
     | 
    
         
            +
                  end
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
                  # creates a new filter class each time it's called, to store the
         
     | 
| 
      
 219 
     | 
    
         
            +
                  # state for this particular filter chain
         
     | 
| 
      
 220 
     | 
    
         
            +
                  def filter
         
     | 
| 
      
 221 
     | 
    
         
            +
                    @backend.filter(@record_ids_key, @associated_class, @parent_klass,
         
     | 
| 
      
 222 
     | 
    
         
            +
                                    @parent_id, @callbacks, @sort_order)
         
     | 
| 
      
 223 
     | 
    
         
            +
                  end
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
                  def self.associated_ids_for(backend, type, klass, name, *these_ids)
         
     | 
| 
      
 226 
     | 
    
         
            +
                    key_type = case type
         
     | 
| 
      
 227 
     | 
    
         
            +
                    when :has_many, :has_and_belongs_to_many
         
     | 
| 
      
 228 
     | 
    
         
            +
                      :set
         
     | 
| 
      
 229 
     | 
    
         
            +
                    when :has_sorted_set
         
     | 
| 
      
 230 
     | 
    
         
            +
                      :sorted_set
         
     | 
| 
      
 231 
     | 
    
         
            +
                    end
         
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
      
 233 
     | 
    
         
            +
                    these_ids.each_with_object({}) do |this_id, memo|
         
     | 
| 
      
 234 
     | 
    
         
            +
                      key = Zermelo::Records::Key.new(
         
     | 
| 
      
 235 
     | 
    
         
            +
                        :klass  => klass,
         
     | 
| 
      
 236 
     | 
    
         
            +
                        :id     => this_id,
         
     | 
| 
      
 237 
     | 
    
         
            +
                        :name   => "#{name}_ids",
         
     | 
| 
      
 238 
     | 
    
         
            +
                        :type   => key_type,
         
     | 
| 
      
 239 
     | 
    
         
            +
                        :object => :association
         
     | 
| 
      
 240 
     | 
    
         
            +
                      )
         
     | 
| 
      
 241 
     | 
    
         
            +
                      memo[this_id] = backend.get(key)
         
     | 
| 
      
 242 
     | 
    
         
            +
                    end
         
     | 
| 
      
 243 
     | 
    
         
            +
                  end
         
     | 
| 
      
 244 
     | 
    
         
            +
             
     | 
| 
      
 245 
     | 
    
         
            +
                end
         
     | 
| 
      
 246 
     | 
    
         
            +
              end
         
     | 
| 
      
 247 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,44 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # NB index instances are all internal to zermelo, not user-accessible
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'zermelo/records/key'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Zermelo
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Associations
         
     | 
| 
      
 7 
     | 
    
         
            +
                class RangeIndex
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(parent_klass, name)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @parent_klass   = parent_klass
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @attribute_name = name
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    @backend   = parent_klass.send(:backend)
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                    parent_klass.send(:with_index_data, name.to_sym) do |data|
         
     | 
| 
      
 16 
     | 
    
         
            +
                      @attribute_type = data.type
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def delete_id(id, value)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @backend.delete(key, id)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def add_id(id, value)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @backend.add(key, [@backend.safe_value(@attribute_type, value), id])
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def move_id(id, value_from, indexer_to, value_to)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    @backend.move(key, [@backend.safe_value(@attribute_type, value_from), id],
         
     | 
| 
      
 30 
     | 
    
         
            +
                      indexer_to.key, [@backend.safe_value(@attribute_type, value_to), id])
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  def key
         
     | 
| 
      
 34 
     | 
    
         
            +
                    @indexer ||= Zermelo::Records::Key.new(
         
     | 
| 
      
 35 
     | 
    
         
            +
                      :klass  => @parent_klass,
         
     | 
| 
      
 36 
     | 
    
         
            +
                      :name   => "by_#{@attribute_name}",
         
     | 
| 
      
 37 
     | 
    
         
            +
                      :type   => :sorted_set,
         
     | 
| 
      
 38 
     | 
    
         
            +
                      :object => :index
         
     | 
| 
      
 39 
     | 
    
         
            +
                    )
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
      
 44 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,193 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Zermelo
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Associations
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Singular
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  def initialize(type, parent_klass, parent_id, name)
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @type = type
         
     | 
| 
      
 7 
     | 
    
         
            +
                    @parent_klass = parent_klass
         
     | 
| 
      
 8 
     | 
    
         
            +
                    @parent_id = parent_id
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @name   = name
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    @backend = parent_klass.send(:backend)
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    @record_id_key = Zermelo::Records::Key.new(
         
     | 
| 
      
 14 
     | 
    
         
            +
                      :klass  => parent_klass,
         
     | 
| 
      
 15 
     | 
    
         
            +
                      :id     => parent_id,
         
     | 
| 
      
 16 
     | 
    
         
            +
                      :name   => type.to_s,
         
     | 
| 
      
 17 
     | 
    
         
            +
                      :type   => :hash,
         
     | 
| 
      
 18 
     | 
    
         
            +
                      :object => :association
         
     | 
| 
      
 19 
     | 
    
         
            +
                    )
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    parent_klass.send(:with_association_data, name.to_sym) do |data|
         
     | 
| 
      
 22 
     | 
    
         
            +
                      @associated_class = data.data_klass
         
     | 
| 
      
 23 
     | 
    
         
            +
                      @lock_klasses     = [data.data_klass] + data.related_klasses
         
     | 
| 
      
 24 
     | 
    
         
            +
                      @inverse          = data.inverse
         
     | 
| 
      
 25 
     | 
    
         
            +
                      @callbacks        = data.callbacks
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    raise ':inverse_of must be set' if @inverse.nil?
         
     | 
| 
      
 29 
     | 
    
         
            +
                    @inverse_key = "#{@inverse}_id"
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  def value=(record)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    if record.nil?
         
     | 
| 
      
 34 
     | 
    
         
            +
                      @parent_klass.lock(*@lock_klasses) do
         
     | 
| 
      
 35 
     | 
    
         
            +
                        _clear(:callbacks => true)
         
     | 
| 
      
 36 
     | 
    
         
            +
                      end
         
     | 
| 
      
 37 
     | 
    
         
            +
                    else
         
     | 
| 
      
 38 
     | 
    
         
            +
                      raise 'Invalid record class' unless record.is_a?(@associated_class)
         
     | 
| 
      
 39 
     | 
    
         
            +
                      raise 'Record must have been saved' unless record.persisted?
         
     | 
| 
      
 40 
     | 
    
         
            +
                      @parent_klass.lock(*@lock_klasses) do
         
     | 
| 
      
 41 
     | 
    
         
            +
                        opts = {:callbacks => true}
         
     | 
| 
      
 42 
     | 
    
         
            +
                        if :sorted_set.eql?(_inverse.type)
         
     | 
| 
      
 43 
     | 
    
         
            +
                          opts[:score] = @parent_klass.find_by_id!(@parent_id).send(@inverse_sort_key.to_sym).to_f
         
     | 
| 
      
 44 
     | 
    
         
            +
                        end
         
     | 
| 
      
 45 
     | 
    
         
            +
                        _set(opts, record.id)
         
     | 
| 
      
 46 
     | 
    
         
            +
                      end
         
     | 
| 
      
 47 
     | 
    
         
            +
                    end
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  def value
         
     | 
| 
      
 51 
     | 
    
         
            +
                    v = nil
         
     | 
| 
      
 52 
     | 
    
         
            +
                    @parent_klass.lock(*@lock_klasses) do
         
     | 
| 
      
 53 
     | 
    
         
            +
                      br = @callbacks[:before_read]
         
     | 
| 
      
 54 
     | 
    
         
            +
                      @parent_klass.send(br, @parent_id) if !br.nil? && @parent_klass.respond_to?(br)
         
     | 
| 
      
 55 
     | 
    
         
            +
                      id = @backend.get(@record_id_key)["#{@name}_id"]
         
     | 
| 
      
 56 
     | 
    
         
            +
                      # # TODO maybe: uses hgetall, need separate getter for hash/list/set
         
     | 
| 
      
 57 
     | 
    
         
            +
                      # @backend.get_hash_value(@record_id_key, "#{@name}_id")
         
     | 
| 
      
 58 
     | 
    
         
            +
                      v = @associated_class.send(:load, id) unless id.nil?
         
     | 
| 
      
 59 
     | 
    
         
            +
                      ar = @callbacks[:after_read]
         
     | 
| 
      
 60 
     | 
    
         
            +
                      @parent_klass.send(ar, @parent_id, v) if !ar.nil? && @parent_klass.respond_to?(ar)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    end
         
     | 
| 
      
 62 
     | 
    
         
            +
                    v
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  private
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  # on_remove already runs inside a lock & transaction
         
     | 
| 
      
 68 
     | 
    
         
            +
                  def on_remove
         
     | 
| 
      
 69 
     | 
    
         
            +
                    _clear(:callbacks => false)
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  def _inverse
         
     | 
| 
      
 73 
     | 
    
         
            +
                    return @inverse_obj unless @inverse_obj.nil?
         
     | 
| 
      
 74 
     | 
    
         
            +
                    @associated_class.send(:with_association_data, @inverse.to_sym) do |data|
         
     | 
| 
      
 75 
     | 
    
         
            +
                      @inverse_obj = case @type
         
     | 
| 
      
 76 
     | 
    
         
            +
                      when :belongs_to
         
     | 
| 
      
 77 
     | 
    
         
            +
                        key_name, key_type = case data.data_type
         
     | 
| 
      
 78 
     | 
    
         
            +
                        when :has_many
         
     | 
| 
      
 79 
     | 
    
         
            +
                          ["#{@inverse}_ids", :set]
         
     | 
| 
      
 80 
     | 
    
         
            +
                        when :has_sorted_set
         
     | 
| 
      
 81 
     | 
    
         
            +
                          ["#{@inverse}_ids", :sorted_set]
         
     | 
| 
      
 82 
     | 
    
         
            +
                        when :has_one
         
     | 
| 
      
 83 
     | 
    
         
            +
                          ["has_one", :hash]
         
     | 
| 
      
 84 
     | 
    
         
            +
                        end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                        @inverse_sort_key = data.sort_key
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                        Zermelo::Records::Key.new(
         
     | 
| 
      
 89 
     | 
    
         
            +
                          :klass  => @associated_class,
         
     | 
| 
      
 90 
     | 
    
         
            +
                          :name   => key_name,
         
     | 
| 
      
 91 
     | 
    
         
            +
                          :type   => key_type,
         
     | 
| 
      
 92 
     | 
    
         
            +
                          :object => :association
         
     | 
| 
      
 93 
     | 
    
         
            +
                        )
         
     | 
| 
      
 94 
     | 
    
         
            +
                      when :has_one
         
     | 
| 
      
 95 
     | 
    
         
            +
                        # inverse is belongs_to
         
     | 
| 
      
 96 
     | 
    
         
            +
                        Zermelo::Records::Key.new(
         
     | 
| 
      
 97 
     | 
    
         
            +
                          :klass  => @associated_class,
         
     | 
| 
      
 98 
     | 
    
         
            +
                          :name   => 'belongs_to',
         
     | 
| 
      
 99 
     | 
    
         
            +
                          :type   => :hash,
         
     | 
| 
      
 100 
     | 
    
         
            +
                          :object => :association
         
     | 
| 
      
 101 
     | 
    
         
            +
                        )
         
     | 
| 
      
 102 
     | 
    
         
            +
                      end
         
     | 
| 
      
 103 
     | 
    
         
            +
                    end
         
     | 
| 
      
 104 
     | 
    
         
            +
                    @inverse_obj
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                  def _clear(opts = {})
         
     | 
| 
      
 108 
     | 
    
         
            +
                    bc = @callbacks[:before_clear]
         
     | 
| 
      
 109 
     | 
    
         
            +
                    if bc.nil? || !opts[:callbacks] || !@parent_klass.respond_to?(bc) ||
         
     | 
| 
      
 110 
     | 
    
         
            +
                      !@parent_klass.send(bc, @parent_id).is_a?(FalseClass)
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                      record_id = @backend.get(@record_id_key)["#{@name}_id"]
         
     | 
| 
      
 113 
     | 
    
         
            +
                      _inverse.id = record_id
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                      new_txn = @backend.begin_transaction
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                      case @type
         
     | 
| 
      
 118 
     | 
    
         
            +
                      when :belongs_to, :has_one
         
     | 
| 
      
 119 
     | 
    
         
            +
                        # FIXME can we access the assoc type instead?
         
     | 
| 
      
 120 
     | 
    
         
            +
                        case _inverse.type
         
     | 
| 
      
 121 
     | 
    
         
            +
                        when :set, :sorted_set
         
     | 
| 
      
 122 
     | 
    
         
            +
                          @backend.delete(_inverse, @parent_id)
         
     | 
| 
      
 123 
     | 
    
         
            +
                        when :hash
         
     | 
| 
      
 124 
     | 
    
         
            +
                          @backend.delete(_inverse, "#{@inverse}_id")
         
     | 
| 
      
 125 
     | 
    
         
            +
                        end
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                        @backend.delete(@record_id_key, "#{@name}_id")
         
     | 
| 
      
 128 
     | 
    
         
            +
                      end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                      @backend.commit_transaction if new_txn
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                      ac = @callbacks[:after_clear]
         
     | 
| 
      
 133 
     | 
    
         
            +
                      if !ac.nil? && opts[:callbacks] && @parent_klass.respond_to?(ac)
         
     | 
| 
      
 134 
     | 
    
         
            +
                        @parent_klass.send(ac, @parent_id, record_id)
         
     | 
| 
      
 135 
     | 
    
         
            +
                      end
         
     | 
| 
      
 136 
     | 
    
         
            +
                    end
         
     | 
| 
      
 137 
     | 
    
         
            +
                  end
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                  def _set(opts = {}, record_id)
         
     | 
| 
      
 140 
     | 
    
         
            +
                    bs = @callbacks[:before_set]
         
     | 
| 
      
 141 
     | 
    
         
            +
                    if bs.nil? || !opts[:callbacks] || !@parent_klass.respond_to?(bs) ||
         
     | 
| 
      
 142 
     | 
    
         
            +
                      !@parent_klass.send(bs, @parent_id, record_id).is_a?(FalseClass)
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                      _inverse.id = record_id
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                      new_txn = @backend.begin_transaction
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                      # FIXME can we access the assoc type instead?
         
     | 
| 
      
 149 
     | 
    
         
            +
                      case _inverse.type
         
     | 
| 
      
 150 
     | 
    
         
            +
                      when :set
         
     | 
| 
      
 151 
     | 
    
         
            +
                        @backend.add(_inverse, @parent_id)
         
     | 
| 
      
 152 
     | 
    
         
            +
                      when :sorted_set
         
     | 
| 
      
 153 
     | 
    
         
            +
                        @backend.add(_inverse, [opts[:score], @parent_id])
         
     | 
| 
      
 154 
     | 
    
         
            +
                      when :hash
         
     | 
| 
      
 155 
     | 
    
         
            +
                        @backend.add(_inverse, @inverse_key => @parent_id)
         
     | 
| 
      
 156 
     | 
    
         
            +
                      end
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                      @backend.add(@record_id_key, "#{@name}_id" => record_id)
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                      @backend.commit_transaction if new_txn
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
                      as = @callbacks[:after_set]
         
     | 
| 
      
 163 
     | 
    
         
            +
                      if !as.nil? && opts[:callbacks] && @parent_klass.respond_to?(as)
         
     | 
| 
      
 164 
     | 
    
         
            +
                        @parent_klass.send(as, @parent_id, record_id)
         
     | 
| 
      
 165 
     | 
    
         
            +
                      end
         
     | 
| 
      
 166 
     | 
    
         
            +
                    end
         
     | 
| 
      
 167 
     | 
    
         
            +
                  end
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                  def self.associated_ids_for(backend, type, klass, name, inversed, *these_ids)
         
     | 
| 
      
 170 
     | 
    
         
            +
                    these_ids.each_with_object({}) do |this_id, memo|
         
     | 
| 
      
 171 
     | 
    
         
            +
                      key = Zermelo::Records::Key.new(
         
     | 
| 
      
 172 
     | 
    
         
            +
                        :klass  => klass,
         
     | 
| 
      
 173 
     | 
    
         
            +
                        :id     => this_id,
         
     | 
| 
      
 174 
     | 
    
         
            +
                        :name   => type.to_s,
         
     | 
| 
      
 175 
     | 
    
         
            +
                        :type   => :hash,
         
     | 
| 
      
 176 
     | 
    
         
            +
                        :object => :association
         
     | 
| 
      
 177 
     | 
    
         
            +
                      )
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                      assoc_id = backend.get(key)["#{name}_id"]
         
     | 
| 
      
 180 
     | 
    
         
            +
                      # assoc_id = backend.get_hash_value(key, "#{name}_id")
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
                      if inversed
         
     | 
| 
      
 183 
     | 
    
         
            +
                        memo[assoc_id] ||= []
         
     | 
| 
      
 184 
     | 
    
         
            +
                        memo[assoc_id] << this_id
         
     | 
| 
      
 185 
     | 
    
         
            +
                      else
         
     | 
| 
      
 186 
     | 
    
         
            +
                        memo[this_id] = assoc_id
         
     | 
| 
      
 187 
     | 
    
         
            +
                      end
         
     | 
| 
      
 188 
     | 
    
         
            +
                    end
         
     | 
| 
      
 189 
     | 
    
         
            +
                  end
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                end
         
     | 
| 
      
 192 
     | 
    
         
            +
              end
         
     | 
| 
      
 193 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,5 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # NB index instances are all internal to zermelo, not user-accessible
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            require 'zermelo/records/key'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
       3 
5 
     | 
    
         
             
            module Zermelo
         
     | 
| 
       4 
6 
     | 
    
         
             
              module Associations
         
     | 
| 
       5 
7 
     | 
    
         
             
                class UniqueIndex
         
     | 
| 
         @@ -10,8 +12,6 @@ module Zermelo 
     | 
|
| 
       10 
12 
     | 
    
         | 
| 
       11 
13 
     | 
    
         
             
                    @backend   = parent_klass.send(:backend)
         
     | 
| 
       12 
14 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
                    @indexers = {}
         
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
15 
     | 
    
         
             
                    parent_klass.send(:with_index_data, name.to_sym) do |data|
         
     | 
| 
       16 
16 
     | 
    
         
             
                      @attribute_type = data.type
         
     | 
| 
       17 
17 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -26,7 +26,8 @@ module Zermelo 
     | 
|
| 
       26 
26 
     | 
    
         
             
                  end
         
     | 
| 
       27 
27 
     | 
    
         | 
| 
       28 
28 
     | 
    
         
             
                  def move_id(id, value_from, indexer_to, value_to)
         
     | 
| 
       29 
     | 
    
         
            -
                    @backend.move(key, {@backend.index_keys(@attribute_type,  
     | 
| 
      
 29 
     | 
    
         
            +
                    @backend.move(key, {@backend.index_keys(@attribute_type, value_from).join(':') => id},
         
     | 
| 
      
 30 
     | 
    
         
            +
                      indexer_to.key, {@backend.index_keys(@attribute_type, value_to).join(':') => id})
         
     | 
| 
       30 
31 
     | 
    
         
             
                  end
         
     | 
| 
       31 
32 
     | 
    
         | 
| 
       32 
33 
     | 
    
         
             
                  def key
         
     | 
| 
         @@ -0,0 +1,120 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'active_support/concern'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'zermelo/locks/no_lock'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Zermelo
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              module Backend
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                extend ActiveSupport::Concern
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def escape_key_name(name)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  name.gsub(/%/, '%%').gsub(/ /, '%20').gsub(/:/, '%3A')
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def unescape_key_name(name)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  name.gsub(/%3A/, ':').gsub(/%20/, ' ').gsub(/%%/, '%')
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def safe_value(type, value)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  case type
         
     | 
| 
      
 21 
     | 
    
         
            +
                  when :string, :integer
         
     | 
| 
      
 22 
     | 
    
         
            +
                    value.to_s
         
     | 
| 
      
 23 
     | 
    
         
            +
                  when :float, :timestamp
         
     | 
| 
      
 24 
     | 
    
         
            +
                    value.to_f
         
     | 
| 
      
 25 
     | 
    
         
            +
                  when :boolean
         
     | 
| 
      
 26 
     | 
    
         
            +
                    (!!value).to_s
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def index_keys(type, value)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  return ["null", "null"] if value.nil?
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  case type
         
     | 
| 
      
 34 
     | 
    
         
            +
                  when :string
         
     | 
| 
      
 35 
     | 
    
         
            +
                    ["string", escape_key_name(value)]
         
     | 
| 
      
 36 
     | 
    
         
            +
                  when :integer
         
     | 
| 
      
 37 
     | 
    
         
            +
                    ["integer", escape_key_name(value.to_s)]
         
     | 
| 
      
 38 
     | 
    
         
            +
                  when :float
         
     | 
| 
      
 39 
     | 
    
         
            +
                    ["float", escape_key_name(value.to_s)]
         
     | 
| 
      
 40 
     | 
    
         
            +
                  when :timestamp
         
     | 
| 
      
 41 
     | 
    
         
            +
                    case value
         
     | 
| 
      
 42 
     | 
    
         
            +
                    when Integer
         
     | 
| 
      
 43 
     | 
    
         
            +
                      ["timestamp", escape_key_name(value.to_s)]
         
     | 
| 
      
 44 
     | 
    
         
            +
                    when Time, DateTime
         
     | 
| 
      
 45 
     | 
    
         
            +
                      ["timestamp", escape_key_name(value.to_f.to_s)]
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
                  when :boolean
         
     | 
| 
      
 48 
     | 
    
         
            +
                    case value
         
     | 
| 
      
 49 
     | 
    
         
            +
                    when TrueClass
         
     | 
| 
      
 50 
     | 
    
         
            +
                      ["boolean", "true"]
         
     | 
| 
      
 51 
     | 
    
         
            +
                    when FalseClass
         
     | 
| 
      
 52 
     | 
    
         
            +
                      ["boolean", "false"]
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                # for hashes, lists, sets
         
     | 
| 
      
 58 
     | 
    
         
            +
                def add(key, value)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  change(:add, key, value)
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def delete(key, value)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  change(:delete, key, value)
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                def move(key_from, value_from, key_to, value_to)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  change(:move, key_from, value_from, key_to, value_to)
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                def clear(key)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  change(:clear, key)
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                # works for both simple and complex types (i.e. strings, numbers, booleans,
         
     | 
| 
      
 75 
     | 
    
         
            +
                #  hashes, lists, sets)
         
     | 
| 
      
 76 
     | 
    
         
            +
                def set(key, value)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  change(:set, key, value)
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                def purge(key)
         
     | 
| 
      
 81 
     | 
    
         
            +
                  change(:purge, key)
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                def get(attr_key)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  get_multiple(attr_key)[attr_key.klass.send(:class_key)][attr_key.id][attr_key.name.to_s]
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                def lock(*klasses, &block)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  ret = nil
         
     | 
| 
      
 90 
     | 
    
         
            +
                  # doesn't handle re-entrant case for influxdb, which has no locking yet
         
     | 
| 
      
 91 
     | 
    
         
            +
                  locking = Thread.current[:zermelo_locking]
         
     | 
| 
      
 92 
     | 
    
         
            +
                  if locking.nil?
         
     | 
| 
      
 93 
     | 
    
         
            +
                    lock_proc = proc do
         
     | 
| 
      
 94 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 95 
     | 
    
         
            +
                        Thread.current[:zermelo_locking] = klasses
         
     | 
| 
      
 96 
     | 
    
         
            +
                        ret = block.call
         
     | 
| 
      
 97 
     | 
    
         
            +
                      ensure
         
     | 
| 
      
 98 
     | 
    
         
            +
                        Thread.current[:zermelo_locking] = nil
         
     | 
| 
      
 99 
     | 
    
         
            +
                      end
         
     | 
| 
      
 100 
     | 
    
         
            +
                    end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                    lock_klass = case self
         
     | 
| 
      
 103 
     | 
    
         
            +
                    when Zermelo::Backends::Redis
         
     | 
| 
      
 104 
     | 
    
         
            +
                      Zermelo::Locks::RedisLock
         
     | 
| 
      
 105 
     | 
    
         
            +
                    else
         
     | 
| 
      
 106 
     | 
    
         
            +
                      Zermelo::Locks::NoLock
         
     | 
| 
      
 107 
     | 
    
         
            +
                    end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                    lock_klass.new.lock(*klasses, &lock_proc)
         
     | 
| 
      
 110 
     | 
    
         
            +
                  else
         
     | 
| 
      
 111 
     | 
    
         
            +
                    # accepts any subset of 'locking'
         
     | 
| 
      
 112 
     | 
    
         
            +
                    unless (klasses - locking).empty?
         
     | 
| 
      
 113 
     | 
    
         
            +
                      raise "Currently locking #{locking.map(&:name)}, cannot lock different set #{klasses.map(&:name)}"
         
     | 
| 
      
 114 
     | 
    
         
            +
                    end
         
     | 
| 
      
 115 
     | 
    
         
            +
                    ret = block.call
         
     | 
| 
      
 116 
     | 
    
         
            +
                  end
         
     | 
| 
      
 117 
     | 
    
         
            +
                  ret
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
              end
         
     | 
| 
      
 120 
     | 
    
         
            +
            end
         
     |