post_json 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +194 -0
- data/Rakefile +21 -0
- data/lib/core_ext/abstract_adapter_extend.rb +16 -0
- data/lib/core_ext/active_record_relation_extend.rb +16 -0
- data/lib/core_ext/hash_extend.rb +36 -0
- data/lib/generators/post_json/install/install_generator.rb +32 -0
- data/lib/generators/post_json/install/templates/create_post_json_documents.rb +13 -0
- data/lib/generators/post_json/install/templates/create_post_json_dynamic_indexes.rb +9 -0
- data/lib/generators/post_json/install/templates/create_post_json_model_settings.rb +18 -0
- data/lib/generators/post_json/install/templates/create_procedures.rb +120 -0
- data/lib/generators/post_json/install/templates/enable_extensions.rb +28 -0
- data/lib/generators/post_json/install/templates/initializer.rb +9 -0
- data/lib/post_json.rb +56 -0
- data/lib/post_json/base.rb +278 -0
- data/lib/post_json/concerns/argument_methods.rb +33 -0
- data/lib/post_json/concerns/dynamic_index_methods.rb +34 -0
- data/lib/post_json/concerns/finder_methods.rb +343 -0
- data/lib/post_json/concerns/query_methods.rb +157 -0
- data/lib/post_json/concerns/settings_methods.rb +106 -0
- data/lib/post_json/dynamic_index.rb +99 -0
- data/lib/post_json/model_settings.rb +17 -0
- data/lib/post_json/query_translator.rb +48 -0
- data/lib/post_json/version.rb +3 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +30 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +26 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/post_json.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +56 -0
- data/spec/dummy/db/migrate/20131015022029_enable_extensions.rb +28 -0
- data/spec/dummy/db/migrate/20131015022030_create_procedures.rb +120 -0
- data/spec/dummy/db/migrate/20131015022031_create_post_json_model_settings.rb +18 -0
- data/spec/dummy/db/migrate/20131015022032_create_post_json_collections.rb +16 -0
- data/spec/dummy/db/migrate/20131015022033_create_post_json_documents.rb +13 -0
- data/spec/dummy/db/migrate/20131015022034_create_post_json_dynamic_indexes.rb +9 -0
- data/spec/dummy/db/structure.sql +311 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/models/base_spec.rb +393 -0
- data/spec/models/collection_spec.rb +27 -0
- data/spec/models/queries_spec.rb +164 -0
- data/spec/modules/argument_methods_spec.rb +17 -0
- data/spec/modules/query_methods_spec.rb +69 -0
- data/spec/spec_helper.rb +54 -0
- metadata +184 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
class EnableExtensions < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
# Install Postgresql 9.2 with PLV8 (might require )
|
4
|
+
# 1. sudo add-apt-repository ppa:pitti/postgresql
|
5
|
+
# 2. sudo apt-get update
|
6
|
+
# 3. sudo apt-get install postgresql-9.2 postgresql-contrib-9.2 postgresql-server-dev-9.2 libv8-dev
|
7
|
+
# 4. sudo su - postgres
|
8
|
+
# 5. Enter psql and run "CREATE USER webnuts WITH PASSWORD 'webnuts';" and "ALTER USER webnuts CREATEDB;" and "ALTER USER webnuts SUPERUSER;"
|
9
|
+
# 6. git clone https://code.google.com/p/plv8js/
|
10
|
+
# 7. cd plv8js
|
11
|
+
# 8. make
|
12
|
+
# 9. sudo make install
|
13
|
+
|
14
|
+
# bundle exec rake db:create:all
|
15
|
+
# bundle exec rake db:migrate
|
16
|
+
# bundle exec rake db:test:prepare
|
17
|
+
|
18
|
+
# See all available extensions:
|
19
|
+
# ActiveRecord::Base.connection.execute("select * from pg_available_extensions;").each_row do |row|
|
20
|
+
# puts row.to_s
|
21
|
+
# end
|
22
|
+
|
23
|
+
enable_extension 'uuid-ossp' # generate universally unique identifiers (UUIDs)
|
24
|
+
enable_extension 'hstore' # data type for storing sets of (key, value) pairs
|
25
|
+
enable_extension 'plv8' # PL/JavaScript (v8) trusted procedural language
|
26
|
+
#enable_extension 'plcoffee' # PL/CoffeeScript (v8) trusted procedural language
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# PostJson.setup "people" do |collection|
|
2
|
+
# collection.record_timestamps = true # default is 'true'
|
3
|
+
# collection.created_at_attribute_name = "created_at" # default is 'created_at'
|
4
|
+
# collection.updated_at_attribute_name = "updated_at" # default is 'updated_at'
|
5
|
+
# collection.include_version_number = true # default is 'true'
|
6
|
+
# collection.version_attribute_name = "version" # default is 'version'
|
7
|
+
# collection.use_dynamic_index = true # default is 'true'
|
8
|
+
# collection.create_dynamic_index_milliseconds_threshold = 50 # default is '50'
|
9
|
+
# end
|
data/lib/post_json.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'core_ext/hash_extend'
|
2
|
+
require 'core_ext/abstract_adapter_extend'
|
3
|
+
require 'core_ext/active_record_relation_extend'
|
4
|
+
require 'post_json/concerns/argument_methods'
|
5
|
+
require 'post_json/concerns/query_methods'
|
6
|
+
require 'post_json/concerns/finder_methods'
|
7
|
+
require 'post_json/concerns/settings_methods'
|
8
|
+
require 'post_json/concerns/dynamic_index_methods'
|
9
|
+
require 'post_json/query_translator'
|
10
|
+
require 'post_json/base'
|
11
|
+
require 'post_json/model_settings'
|
12
|
+
require 'post_json/dynamic_index'
|
13
|
+
require 'post_json/version'
|
14
|
+
|
15
|
+
module PostJson
|
16
|
+
class << self
|
17
|
+
def setup(collection_name, &block)
|
18
|
+
collection = Collection[collection_name]
|
19
|
+
collection.transaction do
|
20
|
+
block.call(collection)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Collection
|
26
|
+
module Proxy
|
27
|
+
class << self
|
28
|
+
def const_missing(class_name)
|
29
|
+
const_set(class_name, Class.new(PostJson::Base))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class << self
|
35
|
+
def [](collection_name)
|
36
|
+
name_digest = PostJson::ModelSettings.collection_name_digest(collection_name)
|
37
|
+
class_name = "Collection_#{name_digest}"
|
38
|
+
model_class = Proxy.const_get(class_name)
|
39
|
+
model_class.collection_name = collection_name
|
40
|
+
model_class
|
41
|
+
end
|
42
|
+
|
43
|
+
def names
|
44
|
+
ModelSettings.order('collection_name').pluck('collection_name')
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_a
|
48
|
+
names.map { |collection_name| self[collection_name] }
|
49
|
+
end
|
50
|
+
|
51
|
+
def each(&block)
|
52
|
+
to_a.each(&block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
module PostJson
|
2
|
+
class Base < ActiveRecord::Base
|
3
|
+
self.abstract_class = true
|
4
|
+
self.table_name = "post_json_documents"
|
5
|
+
self.lock_optimistically = false
|
6
|
+
|
7
|
+
include SettingsMethods
|
8
|
+
include DynamicIndexMethods
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
__local__primary_key = self.class.primary_key
|
12
|
+
__local__attrs = (args[0] || {}).with_indifferent_access
|
13
|
+
__local__attrs[__local__primary_key] = __local__attrs[__local__primary_key].to_s if __local__attrs.has_key?(__local__primary_key)
|
14
|
+
args[0] = {__local__primary_key => __local__attrs[__local__primary_key], '__doc__body' => __local__attrs}.with_indifferent_access
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def cache_key
|
19
|
+
@dashed_name ||= self.class.name.underscore.dasherize
|
20
|
+
__local__unique_version = __doc__version || Digest::MD5.hexdigest(attributes.inspect)
|
21
|
+
"#{@dashed_name}-#{id}-version-#{__local__unique_version}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def attributes
|
25
|
+
read_attribute('__doc__body').try(:with_indifferent_access) || HashWithIndifferentAccess.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_h
|
29
|
+
attributes.deep_dup
|
30
|
+
end
|
31
|
+
|
32
|
+
def write_attribute(attribute_name, value)
|
33
|
+
attribute_name = attribute_name.to_s
|
34
|
+
if attribute_name == '__doc__body'
|
35
|
+
value = value.try(:with_indifferent_access)
|
36
|
+
self.__doc__body_will_change! unless self.__doc__body.try(:with_indifferent_access) == value
|
37
|
+
super('__doc__body', value)
|
38
|
+
elsif attribute_name.in?(attribute_names)
|
39
|
+
super
|
40
|
+
else
|
41
|
+
__doc__body_write_attribute(attribute_name, value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def attribute_changed?(attribute_name)
|
46
|
+
attribute_name = attribute_name.to_s
|
47
|
+
if attribute_name.in?(attribute_names)
|
48
|
+
super
|
49
|
+
else
|
50
|
+
__doc__body_attribute_changed?(attribute_name)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def __doc__body_read_attribute(attribute_name)
|
55
|
+
__local__value = self.__doc__body[attribute_name.to_s] if self.__doc__body
|
56
|
+
__doc__body_convert_attribute_type(attribute_name, __local__value)
|
57
|
+
end
|
58
|
+
|
59
|
+
def __doc__body_write_attribute(attribute_name, value)
|
60
|
+
self.__doc__body = HashWithIndifferentAccess.new(self.__doc__body).merge(attribute_name.to_s => value)
|
61
|
+
value
|
62
|
+
end
|
63
|
+
|
64
|
+
def __doc__body_attribute_was(attribute_name)
|
65
|
+
self.__doc__body_was == nil ? nil : self.__doc__body_was.with_indifferent_access[attribute_name]
|
66
|
+
end
|
67
|
+
|
68
|
+
def __doc__body_attribute_changed?(attribute_name)
|
69
|
+
(self.__doc__body == nil ? nil : self.__doc__body.with_indifferent_access[attribute_name]) != self.__doc__body_attribute_was(attribute_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def __doc__body_attribute_change(attribute_name)
|
73
|
+
__local__change = [__doc__body_attribute_was(attribute_name), __doc__body_read_attribute(attribute_name)]
|
74
|
+
if __local__change[0] == __local__change[1]
|
75
|
+
nil
|
76
|
+
else
|
77
|
+
__local__change
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def __doc__body_convert_attribute_type(attribute_name, value)
|
82
|
+
case value
|
83
|
+
when /^[0-9]{4}-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]\.[0-9]{3}Z$/
|
84
|
+
Time.parse(value).in_time_zone
|
85
|
+
when Hash
|
86
|
+
value.inject(HashWithIndifferentAccess.new) do |result, (key, value)|
|
87
|
+
result[key] = convert_document_attribute_type("#{attribute_name}.#{key}", value)
|
88
|
+
result
|
89
|
+
end
|
90
|
+
when Array
|
91
|
+
value.map.with_index do |array_value, index|
|
92
|
+
convert_document_attribute_type("#{attribute_name}[#{index}]", array_value)
|
93
|
+
end
|
94
|
+
else
|
95
|
+
value
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def [](attribute_name)
|
100
|
+
self.__doc__body_read_attribute(attribute_name)
|
101
|
+
end
|
102
|
+
|
103
|
+
def []=(attribute_name, value)
|
104
|
+
self.__doc__body_write_attribute(attribute_name, value)
|
105
|
+
end
|
106
|
+
|
107
|
+
def respond_to?(method_symbol, include_all = false)
|
108
|
+
if super
|
109
|
+
true
|
110
|
+
else
|
111
|
+
method_name = method_symbol.to_s
|
112
|
+
attribute_name = if method_name.end_with?("_changed?")
|
113
|
+
method_name[0..-10]
|
114
|
+
elsif method_name.end_with?("_was")
|
115
|
+
method_name[0..-5]
|
116
|
+
elsif method_name.end_with?("=")
|
117
|
+
method_name[0..-2]
|
118
|
+
elsif method_name.end_with?("_change")
|
119
|
+
method_name[0..-8]
|
120
|
+
else
|
121
|
+
method_name
|
122
|
+
end
|
123
|
+
attributes.has_key?(attribute_name)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def method_missing(method_symbol, *args, &block)
|
128
|
+
method_name = method_symbol.to_s
|
129
|
+
attribute_name = if method_name.end_with?("_changed?")
|
130
|
+
method_name[0..-10]
|
131
|
+
elsif method_name.end_with?("_was")
|
132
|
+
method_name[0..-5]
|
133
|
+
elsif method_name.end_with?("=")
|
134
|
+
method_name[0..-2]
|
135
|
+
elsif method_name.end_with?("_change")
|
136
|
+
method_name[0..-8]
|
137
|
+
else
|
138
|
+
method_name
|
139
|
+
end
|
140
|
+
|
141
|
+
if attribute_name.in?(attribute_names) == false
|
142
|
+
self.class.define_attribute_accessor(attribute_name)
|
143
|
+
send(method_symbol, *args)
|
144
|
+
else
|
145
|
+
super
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class << self
|
150
|
+
def post_json_all
|
151
|
+
QueryTranslator.new(original_all)
|
152
|
+
end
|
153
|
+
|
154
|
+
alias_method :original_all, :all
|
155
|
+
alias_method :all, :post_json_all
|
156
|
+
|
157
|
+
def page(*args)
|
158
|
+
all.page(*args)
|
159
|
+
end
|
160
|
+
|
161
|
+
def default_scopes
|
162
|
+
if @collection_name
|
163
|
+
model_settings = ModelSettings.table_name
|
164
|
+
# query = original_all.joins("INNER JOIN #{model_settings} ON #{table_name}.__doc__model_settings_id = #{model_settings}.id")
|
165
|
+
# query = query.where("lower(#{model_settings}.collection_name) = ?", collection_name.downcase)
|
166
|
+
|
167
|
+
query = original_all.joins("INNER JOIN #{model_settings} ON lower(#{model_settings}.collection_name) = '#{collection_name.downcase}'")
|
168
|
+
query = query.where("#{table_name}.__doc__model_settings_id = #{model_settings}.id")
|
169
|
+
|
170
|
+
[Proc.new { query }] + super
|
171
|
+
else
|
172
|
+
[Proc.new { none }] + super
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def collection_name
|
177
|
+
message = "You need to assign a collection name to class \"#{name}\":
|
178
|
+
class #{name}
|
179
|
+
self.collection_name = \"people\"
|
180
|
+
end"
|
181
|
+
raise ArgumentError, message unless @collection_name.present?
|
182
|
+
@collection_name
|
183
|
+
end
|
184
|
+
|
185
|
+
def collection_name=(name)
|
186
|
+
raise ArgumentError, "Collection name must be present" unless name.present?
|
187
|
+
@collection_name = name.to_s.strip
|
188
|
+
end
|
189
|
+
|
190
|
+
def rename_collection(new_name)
|
191
|
+
new_name = new_name.to_s.strip
|
192
|
+
settings = find_settings
|
193
|
+
if settings
|
194
|
+
settings.collection_name = new_name
|
195
|
+
settings.save!
|
196
|
+
end
|
197
|
+
@collection_name = new_name
|
198
|
+
end
|
199
|
+
|
200
|
+
def define_attribute_accessor(attribute_name)
|
201
|
+
class_eval <<-RUBY
|
202
|
+
def #{attribute_name}
|
203
|
+
__doc__body_read_attribute('#{attribute_name}')
|
204
|
+
end
|
205
|
+
|
206
|
+
def #{attribute_name}=(value)
|
207
|
+
__doc__body_write_attribute('#{attribute_name}', value)
|
208
|
+
end
|
209
|
+
|
210
|
+
def #{attribute_name}_changed?
|
211
|
+
__doc__body_attribute_changed?('#{attribute_name}')
|
212
|
+
end
|
213
|
+
|
214
|
+
def #{attribute_name}_was
|
215
|
+
__doc__body_attribute_was('#{attribute_name}')
|
216
|
+
end
|
217
|
+
|
218
|
+
def #{attribute_name}_change
|
219
|
+
__doc__body_attribute_change('#{attribute_name}')
|
220
|
+
end
|
221
|
+
RUBY
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
protected
|
226
|
+
|
227
|
+
def timestamp_attributes_for_update
|
228
|
+
[] # ActiveRecord depend on real table columns, so we use an alternative timestamps method
|
229
|
+
end
|
230
|
+
|
231
|
+
def timestamp_attributes_for_create
|
232
|
+
[] # ActiveRecord depend on real table columns, so we use an alternative timestamps method
|
233
|
+
end
|
234
|
+
|
235
|
+
def create_record
|
236
|
+
self.id = self.__doc__body['id'].to_s.strip.downcase
|
237
|
+
if self.id.blank?
|
238
|
+
self.id = self.__doc__body['id'] = SecureRandom.uuid
|
239
|
+
end
|
240
|
+
|
241
|
+
self.__doc__model_settings_id = __model__settings.id
|
242
|
+
self.__doc__version = 1
|
243
|
+
|
244
|
+
if __model__settings.include_version_number == true &&
|
245
|
+
__doc__body_read_attribute(__model__settings.version_attribute_name) == nil
|
246
|
+
__doc__body_write_attribute(__model__settings.version_attribute_name, self.__doc__version)
|
247
|
+
end
|
248
|
+
|
249
|
+
if __model__settings.use_timestamps
|
250
|
+
__local__current_time = Time.zone.now.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
|
251
|
+
__doc__body_write_attribute(__model__settings.created_at_attribute_name, __local__current_time)
|
252
|
+
__doc__body_write_attribute(__model__settings.updated_at_attribute_name, __local__current_time)
|
253
|
+
end
|
254
|
+
super
|
255
|
+
end
|
256
|
+
|
257
|
+
def update_record(*args)
|
258
|
+
if self.changed_attributes.keys.include?(self.class.primary_key)
|
259
|
+
raise ArgumentError, "Primary key '#{self.class.primary_key}' cannot be modified."
|
260
|
+
end
|
261
|
+
|
262
|
+
if self.__doc__body_changed?
|
263
|
+
self.__doc__version = self.__doc__version + 1
|
264
|
+
end
|
265
|
+
|
266
|
+
if __model__settings.include_version_number == true &&
|
267
|
+
__doc__body_attribute_changed?(__model__settings.version_attribute_name) == false
|
268
|
+
__doc__body_write_attribute(__model__settings.version_attribute_name, self.__doc__version)
|
269
|
+
end
|
270
|
+
|
271
|
+
if __model__settings.use_timestamps && __doc__body_attribute_changed?(__model__settings.updated_at_attribute_name)
|
272
|
+
__local__current_time = Time.zone.now.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
|
273
|
+
__doc__body_write_attribute(__model__settings.updated_at_attribute_name, __local__current_time)
|
274
|
+
end
|
275
|
+
super
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module PostJson
|
2
|
+
module ArgumentMethods
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def join_arguments(*arguments)
|
6
|
+
arguments = arguments[0] if arguments.length == 1 && arguments[0].is_a?(Array)
|
7
|
+
arguments = arguments[0].split(',') if arguments.length == 1 && arguments[0].is_a?(String)
|
8
|
+
|
9
|
+
arguments = arguments.map do |arg|
|
10
|
+
case arg
|
11
|
+
when nil
|
12
|
+
nil
|
13
|
+
when String
|
14
|
+
arg.strip.gsub(/\s+/, ' ')
|
15
|
+
when Symbol
|
16
|
+
arg.to_s
|
17
|
+
else
|
18
|
+
arg
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
arguments.join(',')
|
23
|
+
end
|
24
|
+
|
25
|
+
def flatten_arguments(*arguments)
|
26
|
+
join_arguments(*arguments).split(',')
|
27
|
+
end
|
28
|
+
|
29
|
+
def assert_valid_indifferent_keys(options, *valid_keys)
|
30
|
+
options.stringify_keys.assert_valid_keys(flatten_arguments(*valid_keys))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module PostJson
|
2
|
+
module DynamicIndexMethods
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def dynamic_indexes
|
7
|
+
settings = find_settings
|
8
|
+
if settings
|
9
|
+
DynamicIndex.indexed_selectors(settings.id)
|
10
|
+
else
|
11
|
+
[]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_dynamic_index(selector)
|
16
|
+
create_dynamic_indexes(selector)
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_dynamic_indexes(*selectors)
|
20
|
+
settings = find_settings_or_create
|
21
|
+
DynamicIndex.ensure_index(settings.id, *selectors).count
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy_dynamic_index(selector)
|
25
|
+
settings = find_settings_or_create
|
26
|
+
if settings
|
27
|
+
DynamicIndex.destroy_index(settings.id, selector)
|
28
|
+
else
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
require "hashie"
|
2
|
+
|
3
|
+
module PostJson
|
4
|
+
module FinderMethods
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
#
|
8
|
+
# This module depends upon method 'execute'
|
9
|
+
#
|
10
|
+
|
11
|
+
include QueryMethods
|
12
|
+
include ArgumentMethods
|
13
|
+
|
14
|
+
def any?
|
15
|
+
execute { |documents| documents.any? }
|
16
|
+
end
|
17
|
+
|
18
|
+
def blank?
|
19
|
+
execute { |documents| documents.blank? }
|
20
|
+
end
|
21
|
+
|
22
|
+
def count(column_name = nil, options = {})
|
23
|
+
selector = if column_name.present?
|
24
|
+
define_selector(column_name)
|
25
|
+
else
|
26
|
+
column_name
|
27
|
+
end
|
28
|
+
execute { |documents| documents.count(selector, options) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete(id_or_array)
|
32
|
+
where(id: id_or_array).delete_all
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete_all(conditions = nil)
|
36
|
+
where(conditions).execute { |documents| documents.delete_all }
|
37
|
+
end
|
38
|
+
|
39
|
+
def destroy(id)
|
40
|
+
execute { |documents| documents.destroy(id) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def destroy_all(conditions = nil)
|
44
|
+
where(conditions).execute { |documents| documents.destroy_all }
|
45
|
+
end
|
46
|
+
|
47
|
+
def empty?
|
48
|
+
execute { |documents| documents.empty? }
|
49
|
+
end
|
50
|
+
|
51
|
+
def exists?(conditions = nil)
|
52
|
+
query = case conditions
|
53
|
+
when nil
|
54
|
+
self
|
55
|
+
when Numeric, String
|
56
|
+
where({id: conditions})
|
57
|
+
else
|
58
|
+
where(conditions)
|
59
|
+
end
|
60
|
+
|
61
|
+
query.execute { |documents| documents.exists? }
|
62
|
+
end
|
63
|
+
|
64
|
+
def find(*args)
|
65
|
+
execute { |documents| documents.find(*args) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def find_by(*args)
|
69
|
+
where(*args).first
|
70
|
+
end
|
71
|
+
|
72
|
+
def find_by!(*args)
|
73
|
+
find_by(*args) or raise ActiveRecord::RecordNotFound
|
74
|
+
end
|
75
|
+
|
76
|
+
def find_each(options = {})
|
77
|
+
execute { |documents| documents.find_each(options) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_in_batches(options = {})
|
81
|
+
execute { |documents| documents.find_in_batches(options) }
|
82
|
+
end
|
83
|
+
|
84
|
+
def first(limit = nil)
|
85
|
+
if limit
|
86
|
+
limit(limit).execute { |documents| documents.first }
|
87
|
+
else
|
88
|
+
execute { |documents| documents.first }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def first!
|
93
|
+
execute { |documents| documents.first! }
|
94
|
+
end
|
95
|
+
|
96
|
+
def first_or_create(attributes = {})
|
97
|
+
attributes = where_values_hash.with_indifferent_access.deep_merge(attributes)
|
98
|
+
first or create(attributes)
|
99
|
+
end
|
100
|
+
|
101
|
+
def first_or_initialize(attributes = {})
|
102
|
+
attributes = where_values_hash.with_indifferent_access.deep_merge(attributes)
|
103
|
+
first or model_class.new(attributes)
|
104
|
+
end
|
105
|
+
|
106
|
+
def ids
|
107
|
+
pluck('id')
|
108
|
+
end
|
109
|
+
|
110
|
+
def last(limit = nil)
|
111
|
+
reverse_order.first(limit)
|
112
|
+
end
|
113
|
+
|
114
|
+
def last!
|
115
|
+
reverse_order.first!
|
116
|
+
end
|
117
|
+
|
118
|
+
def load
|
119
|
+
execute { |documents| documents.load }
|
120
|
+
end
|
121
|
+
|
122
|
+
def many?
|
123
|
+
execute { |documents| documents.many? }
|
124
|
+
end
|
125
|
+
|
126
|
+
def pluck(*selectors)
|
127
|
+
selectors = join_arguments(*selectors)
|
128
|
+
if selectors == ""
|
129
|
+
[]
|
130
|
+
elsif selectors == "*"
|
131
|
+
execute { |documents| documents.pluck("\"#{table_name}\".__doc__body") }
|
132
|
+
elsif selectors == "id"
|
133
|
+
execute { |documents| documents.pluck("\"#{table_name}\".id") }
|
134
|
+
else
|
135
|
+
result = nil
|
136
|
+
execute { |documents| result = documents.pluck("json_selectors('#{selectors}', \"#{table_name}\".__doc__body)") }
|
137
|
+
if selectors.include?(",")
|
138
|
+
result
|
139
|
+
else
|
140
|
+
result.flatten(1)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def select(*selectors)
|
146
|
+
selectors = selectors.flatten(1)
|
147
|
+
if selectors.length == 0
|
148
|
+
[]
|
149
|
+
elsif selectors.length == 1
|
150
|
+
selector = selectors[0]
|
151
|
+
case selector
|
152
|
+
when String, Symbol
|
153
|
+
selector = selector.to_s.gsub(/\s+/, '')
|
154
|
+
if selector == "*"
|
155
|
+
pluck("*").map { |body| body ? Hashie::Mash.new(body) : body }
|
156
|
+
else
|
157
|
+
selectors = selector.split(",")
|
158
|
+
if selectors.length == 1
|
159
|
+
select({selector => selector})
|
160
|
+
else
|
161
|
+
select(selectors)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
when Hash
|
165
|
+
flat_hash = selector.flatten_hash
|
166
|
+
pluck(flat_hash.values).map do |row|
|
167
|
+
flat_body = if flat_hash.keys.length == 1
|
168
|
+
{flat_hash.keys[0] => row}
|
169
|
+
else
|
170
|
+
Hash[flat_hash.keys.zip(row)]
|
171
|
+
end
|
172
|
+
deep_hash = flat_body.deepen_hash
|
173
|
+
Hashie::Mash.new(deep_hash) if deep_hash
|
174
|
+
end
|
175
|
+
else
|
176
|
+
raise ArgumentError, "Invalid argument(s): #{selectors.inspect}"
|
177
|
+
end
|
178
|
+
else
|
179
|
+
select(Hash[selectors.zip(selectors)])
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def size
|
184
|
+
execute { |documents| documents.size }
|
185
|
+
end
|
186
|
+
|
187
|
+
def take(limit = nil)
|
188
|
+
execute { |documents| documents.take(limit) }
|
189
|
+
end
|
190
|
+
|
191
|
+
def take!
|
192
|
+
execute { |documents| documents.take! }
|
193
|
+
end
|
194
|
+
|
195
|
+
def to_a
|
196
|
+
execute { |documents| documents.to_a }
|
197
|
+
end
|
198
|
+
|
199
|
+
def to_sql
|
200
|
+
execute { |documents| documents.to_sql }
|
201
|
+
end
|
202
|
+
|
203
|
+
def where_values_hash
|
204
|
+
where_equals = query_tree[:where_equal] || []
|
205
|
+
values_hash = where_equals.inject({}) do |result, where_equal|
|
206
|
+
key = where_equal[:attribute]
|
207
|
+
result[key] = where_equal[:argument]
|
208
|
+
result
|
209
|
+
end
|
210
|
+
values_hash.deepen_hash
|
211
|
+
end
|
212
|
+
|
213
|
+
protected
|
214
|
+
|
215
|
+
def define_selector(attribute_name)
|
216
|
+
case attribute_name.to_s
|
217
|
+
when "id"
|
218
|
+
"\"#{table_name}\".id"
|
219
|
+
else
|
220
|
+
"json_selector('#{attribute_name}', \"#{table_name}\".__doc__body)"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def prepare_query_tree_for_method_mapping(query_tree)
|
225
|
+
prepared_query_tree = query_tree.map do |method_sym, arguments_collection|
|
226
|
+
arguments_collection.map do |arguments|
|
227
|
+
case method_sym
|
228
|
+
when :limit
|
229
|
+
[:limit, arguments]
|
230
|
+
when :offset
|
231
|
+
[:offset, arguments]
|
232
|
+
when :order
|
233
|
+
name, direction = arguments.split(" ")
|
234
|
+
selector = define_selector(name)
|
235
|
+
[:order, "#{selector} #{direction}"]
|
236
|
+
when :where_function
|
237
|
+
function = arguments[:function]
|
238
|
+
escape_sql_single_quote = function.gsub("'", "''")
|
239
|
+
condition = "js_filter('#{escape_sql_single_quote}', '#{arguments[:arguments]}', \"#{table_name}\".__doc__body) = 1"
|
240
|
+
[:where, condition]
|
241
|
+
when :where_forward
|
242
|
+
if arguments[0].is_a?(String)
|
243
|
+
json_regex = "json_([^ =]+)"
|
244
|
+
arguments[0] = arguments[0].gsub(/^#{json_regex}\ /) {"json_selector('#{$1}', \"#{table_name}\".__doc__body) "}
|
245
|
+
arguments[0] = arguments[0].gsub(/\ #{json_regex}\ /) {" json_selector('#{$1}', \"#{table_name}\".__doc__body) "}
|
246
|
+
arguments[0] = arguments[0].gsub(/\ #{json_regex}$/) {" json_selector('#{$1}', \"#{table_name}\".__doc__body)"}
|
247
|
+
end
|
248
|
+
[:where, arguments]
|
249
|
+
when :where_equal
|
250
|
+
selector = define_selector(arguments[:attribute])
|
251
|
+
argument = arguments[:argument]
|
252
|
+
case argument
|
253
|
+
when Array
|
254
|
+
values = argument.map{|v| v ? v.to_s : nil}
|
255
|
+
[:where, "(#{selector} IN (?))", values]
|
256
|
+
when Range
|
257
|
+
first_value = argument.first ? argument.first.to_s : nil
|
258
|
+
last_value = argument.last ? argument.last.to_s : nil
|
259
|
+
[:where, "(#{selector} BETWEEN ? AND ?)", first_value, last_value]
|
260
|
+
else
|
261
|
+
value = argument ? argument.to_s : nil
|
262
|
+
[:where, "#{selector} = ?", value]
|
263
|
+
end
|
264
|
+
else
|
265
|
+
raise NotImplementedError, "Query tree method '#{method_sym}' not mapped to Active Record."
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
prepared_query_tree.flatten(1)
|
270
|
+
end
|
271
|
+
|
272
|
+
def active_record_send_invocations
|
273
|
+
prepare_query_tree_for_method_mapping(query_tree)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# def query(params)
|
279
|
+
# valid_params = params.slice(:filter, :filter_arguments, :count, :page, :per_page, :order, :limit, :offset, :select)
|
280
|
+
|
281
|
+
# inquiry = self
|
282
|
+
# inquiry = inquiry.where(valid_params[:filter], valid_params[:filter_arguments]) if valid_params[:filter].present?
|
283
|
+
|
284
|
+
# total_count = inquiry.count
|
285
|
+
|
286
|
+
# if valid_params[:count].to_s.downcase.in? ["true", "yes", "1"]
|
287
|
+
# total_count
|
288
|
+
# else
|
289
|
+
# meta = {
|
290
|
+
# page: 1,
|
291
|
+
# per_page: nil,
|
292
|
+
# total_pages: 1,
|
293
|
+
# total_count: total_count,
|
294
|
+
# }
|
295
|
+
|
296
|
+
# page, per_page = valid_params[:page].to_i, valid_params[:per_page].to_i
|
297
|
+
|
298
|
+
# inquiry = if valid_params[:page].present?
|
299
|
+
# total_pages = total_count/per_page
|
300
|
+
# total_pages = total_pages + 1 if 0 < total_count%per_page
|
301
|
+
# meta[:page] = page
|
302
|
+
# meta[:per_page] = per_page
|
303
|
+
# meta[:total_pages] = total_pages
|
304
|
+
# inquiry.page(page, per_page)
|
305
|
+
# else
|
306
|
+
# inquiry
|
307
|
+
# end
|
308
|
+
|
309
|
+
# inquiry = inquiry.order(valid_params[:order]) if valid_params[:order].present?
|
310
|
+
# inquiry = inquiry.limit(valid_params[:limit].to_i) if valid_params[:limit].present?
|
311
|
+
# inquiry = inquiry.offset(valid_params[:offset].to_i) if valid_params[:offset].present?
|
312
|
+
|
313
|
+
# meta[:updated_at] = inquiry.except(:order)
|
314
|
+
# .order('updatedAt desc')
|
315
|
+
# .limit(1)
|
316
|
+
# .select("updatedAt")
|
317
|
+
# .first
|
318
|
+
# .try("updatedAt")
|
319
|
+
|
320
|
+
# bodies = if valid_params[:select].present?
|
321
|
+
# inquiry.select(valid_params[:select])
|
322
|
+
# else
|
323
|
+
# inquiry.pluck("body")
|
324
|
+
# end
|
325
|
+
|
326
|
+
# {documents: bodies, meta: meta}
|
327
|
+
# end
|
328
|
+
# end
|
329
|
+
|
330
|
+
# def digest_etag(seed = nil)
|
331
|
+
# inquery = except(:order).order('id')
|
332
|
+
# digest_seed = (inquery.pluck(:etag) + [inquery.to_sql + seed.to_s]).join
|
333
|
+
# etag = Digest::SHA2.hexdigest(digest_seed)
|
334
|
+
# end
|
335
|
+
|
336
|
+
# def latest_updated_at
|
337
|
+
# except(:order).order('updated_at DESC').limit(1).pluck(:updated_at).first
|
338
|
+
# end
|
339
|
+
|
340
|
+
# def xor_uuids(uuid_array)
|
341
|
+
# # move work to db: http://stackoverflow.com/questions/17739887/how-to-xor-md5-hash-values-and-cast-them-to-hex-in-postgresql
|
342
|
+
# uuid_array.inject("") { |result, uuid| (result.to_i(16) ^ uuid.to_i(16)).to_s(16) }
|
343
|
+
# end
|