post_json 1.0.3
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 +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
|