Class: JOR::Collection

Inherits:
Object
  • Object
show all
Defined in:
lib/jor/collection.rb

Constant Summary collapse

DEFAULT_OPTIONS =
{
  :max_documents => 1000,
  :raw => false,
  :only_ids => false,
  :reversed => false,
  :excluded_fields_to_index => {}
}

Instance Method Summary collapse

Constructor Details

#initialize(storage, name, auto_increment = false) ⇒ Collection

Returns a new instance of Collection.



12
13
14
15
16
# File 'lib/jor/collection.rb', line 12

def initialize(storage, name, auto_increment = false)
  @storage = storage
  @name = name
  @auto_increment = auto_increment
end

Instance Method Details

#auto_increment?Boolean

Returns:

  • (Boolean)


30
31
32
# File 'lib/jor/collection.rb', line 30

def auto_increment?
  @auto_increment
end

#countObject

Raises:



160
161
162
163
# File 'lib/jor/collection.rb', line 160

def count
  raise NotInCollection.new unless name
  redis.zcard(doc_sset_key())
end

#delete(doc) ⇒ Object

Raises:



82
83
84
85
86
87
88
89
# File 'lib/jor/collection.rb', line 82

def delete(doc)
  raise NotInCollection.new unless name
  ids = find(doc, {:only_ids => true, :max_documents => -1})
  ids.each do |id|
    delete_by_id(id)
  end
  ids.size
end

#find(doc, options = {}) ⇒ Object

Raises:



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/jor/collection.rb', line 165

def find(doc, options = {})
  raise NotInCollection.new unless name
  # list of ids of the documents
  ids = []
  opt =  merge_and_symbolize_options(options)

  if opt[:max_documents] >= 0
    num_docs = opt[:max_documents]-1
  else
    num_docs = -1
  end

  ## if doc contains _id it ignores the rest of the doc's fields
  if !doc["_id"].nil? && !doc["_id"].kind_of?(Hash) && doc.size==1
    ids << doc["_id"]
    return [] if opt[:only_ids]==true && redis.get(doc_key(ids.first)).nil?
  elsif (doc == {})
    if (opt[:reversed]==true)
      ids = redis.zrevrange(doc_sset_key(),0,num_docs)
    else
      ids = redis.zrange(doc_sset_key(),0,num_docs)
    end
    ids.map!(&:to_i)
    ##ids = redis.smembers(doc_sset_key())
  else
    paths = Doc.paths("!",doc)
    ## for now, consider all logical and
    paths.each_with_index do |path, i|
      tmp_res = fetch_ids_by_index(path)
      if i==0
        ids = tmp_res
      else
        ids = ids & tmp_res
      end
    end

    ids.map!(&:to_i)
    ids.reverse! if opt[:reversed]
  end

  return [] if ids.nil? || ids.size==0

  ## return only up to max_documents, if max_documents is negative
  ## return them all (they have already been reversed)
  if opt[:max_documents] >= 0 && opt[:max_documents] < ids.size
    ids = ids[0..opt[:max_documents]-1]
  end

  ## return only the ids, it saves fetching the JSON string from
  ## redis and decoding it
  return ids if (opt[:only_ids]==true)

  results = redis.pipelined do
    ids.each do |id|
      redis.get(doc_key(id))
    end
  end

  ## remove nils
  results.delete_if {|i| i == nil}

  ## return the results JSON encoded (raw), many times you do not need the
  ## object but only the JSON string
  return results if (opt[:raw]==true)

  results.map! { |item| JSON::parse(item) }
  return results
end

#indexes(id) ⇒ Object



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/jor/collection.rb', line 250

def indexes(id)
  indexes = redis.smembers(idx_set_key(id))
  res = []
  indexes.each do |ind|
    v = ind.split("!")
    str = v[1..v.size].join("!")

    v = str.split("/")
    type = v[v.size-2]
    path = v[0..v.size-3].join("/")

    if path!=""
      value_tmp = v[v.size-1]
      w = value_tmp.split("_")
      value = w[0..w.size-2].join("_")
      ## returns the value of a Numeric as string, to cast to float or int, cannot
      ## be derived from type because it Numeric
      res << {"path" => path, "obj" => type, "value" => value}
    end
  end
  return res
end

#insert(docs, options = {}) ⇒ Object

Raises:



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/jor/collection.rb', line 34

def insert(docs, options = {})
  raise NotInCollection.new unless name
  opt = merge_and_symbolize_options(options)

  docs.is_a?(Array) ? docs_list = docs : docs_list = [docs]

  docs_list.each_with_index do |doc, i|
    if auto_increment?
      raise DocumentDoesNotNeedId.new(name) unless doc["_id"].nil?
      doc["_id"] = next_id()
    else
      raise DocumentNeedsId.new(name) if doc["_id"].nil?
    end

    doc["_created_at"] ||= Time.now.to_f

    encd = JSON::generate(doc)
    paths = Doc.paths("!",doc)
    id = doc["_id"]

    raise InvalidDocumentId.new(id) if !id.is_a?(Numeric) || id < 0

    if !opt[:excluded_fields_to_index].nil? && opt[:excluded_fields_to_index].size>0
      excluded_paths = Doc.paths("!",opt[:excluded_fields_to_index])
      paths = Doc.difference(paths, excluded_paths)
    end

    redis.watch(doc_key(id))
    exists = redis.get(doc_key(id))

    if !exists.nil?
      redis.multi
      redis.exec
      raise DocumentIdAlreadyExists.new(id, name)
    else
      res = redis.multi do
        redis.set(doc_key(id),encd)
        redis.zadd(doc_sset_key(),id,id)
        paths.each do |path|
          add_index(path,id)
        end
      end
      raise DocumentIdAlreadyExists.new(id, name) unless exists.nil?
    end
  end
  docs
end

#last_idObject



234
235
236
237
238
239
240
241
242
243
244
# File 'lib/jor/collection.rb', line 234

def last_id
  if auto_increment?
    val = redis.get("#{Storage::NAMESPACE}/#{name}/next_id")
    return val.to_i unless val.nil?
    return 0
  else
    val = redis.zrevrange(doc_sset_key(),0,0)
    return 0 if val.nil? || val.size==0
    return val.first.to_i
  end
end

#nameObject



18
19
20
# File 'lib/jor/collection.rb', line 18

def name
  @name
end

#next_idObject



246
247
248
# File 'lib/jor/collection.rb', line 246

def next_id
  redis.incrby("#{Storage::NAMESPACE}/#{name}/next_id",1)
end

#redisObject



22
23
24
# File 'lib/jor/collection.rb', line 22

def redis
  @storage.redis
end

#storageObject



26
27
28
# File 'lib/jor/collection.rb', line 26

def storage
  @storage
end

#update(doc_dest, doc_source, options = {}) ⇒ Object

Raises:



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/jor/collection.rb', line 91

def update(doc_dest, doc_source, options = {})
  raise NotInCollection.new unless name
  doc_source.delete("_id") unless doc_source["_id"].nil?
  opt = merge_and_symbolize_options(options)

  doc_source["_updated_at"] = Time.now.to_f
  paths_all = Doc.paths("!",doc_source)
  excluded_paths = []

  if !opt[:excluded_fields_to_index].nil? && opt[:excluded_fields_to_index].size>0
    excluded_paths = Doc.paths("!",opt[:excluded_fields_to_index])
  end

  paths_to_index = Doc.difference(paths_all, excluded_paths)

  docs = find(doc_dest, {:max_documents => -1})

  results = []

  docs.each do |doc|

    indexes_doc = redis.smembers(idx_set_key(doc["_id"]))

    to_remove = []
    indexes_doc.each do |index_doc|
      ## for each index there is, check if it's affected by the new paths
      path_to_from_index = get_path_to_from_index(index_doc)

      paths_all.each do |path|
        if path["obj"].kind_of?(NilClass)
          if path["path_to"]==path_to_from_index
            to_remove << index_doc
          elsif path_to_from_index.index("#{path["path_to"]}/")!=nil
            to_remove << index_doc
          end
        else
          if path["path_to"]==path_to_from_index
            to_remove << index_doc
          end
        end
      end
    end

    redis.pipelined do
      to_remove.each do |index|
        remove_index(index, doc["_id"])
        redis.srem(idx_set_key(doc["_id"]),index)
      end
    end

    ## now, the indexes that refer to the changed fields are gone
    new_doc = Doc.deep_merge(doc, doc_source)
    encd = JSON::generate(new_doc)

    res = redis.multi do
      redis.set(doc_key(new_doc["_id"]),encd)
      ## note that it's not paths_all but only the ones who are not excluded
      paths_to_index.each do |path|
        add_index(path,new_doc["_id"])
      end
    end

    results << new_doc

  end

  return results
end