montage_rails 0.3.2 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT-LICENSE +20 -0
- data/Rakefile +28 -0
- data/lib/montage_rails/base/column.rb +57 -0
- data/lib/montage_rails/base.rb +406 -0
- data/lib/montage_rails/errors.rb +7 -0
- data/lib/montage_rails/log_subscriber.rb +48 -0
- data/lib/montage_rails/query_cache.rb +44 -0
- data/lib/montage_rails/railtie.rb +0 -0
- data/lib/montage_rails/relation.rb +88 -0
- data/lib/montage_rails/version.rb +3 -0
- data/lib/montage_rails.rb +57 -0
- data/lib/tasks/montage_rails_tasks.rake +4 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/actor.rb +3 -0
- data/test/dummy/app/models/movie.rb +30 -0
- data/test/dummy/app/models/studio.rb +3 -0
- data/test/dummy/app/models/test.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config/application.rb +25 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/montage.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/schema.rb +16 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/RAILS_ENV=development.log +0 -0
- data/test/dummy/log/development.log +1038 -0
- data/test/dummy/log/test.log +12368 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/montage_rails/base/column_test.rb +59 -0
- data/test/montage_rails/base_test.rb +375 -0
- data/test/montage_rails/query_cache_test.rb +68 -0
- data/test/montage_rails/relation_test.rb +114 -0
- data/test/montage_rails_test.rb +97 -0
- data/test/resources/actor_resource.rb +141 -0
- data/test/resources/movie_resource.rb +160 -0
- data/test/resources/studio_resource.rb +56 -0
- data/test/test_helper.rb +196 -0
- metadata +123 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 277cfc7ce7155045e893a43b55fb439cefcc1fb1
|
4
|
+
data.tar.gz: 0b8338daff90f24f126bf6d18daac01205b2a8e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 87b3d4b9ac86ea2310c782091869252e94f23426f2cf226ef23e0fbfc7b27fd8d3f12941731b7226310ba624510a494203ed0d89695b2e4e28e9f7deb9c5166e
|
7
|
+
data.tar.gz: a5a907dceb7a233c2ac321c4984c24a193c91292dc5be1263f473278d0aa6e2f905dd3a9df19aca4b8fbcffd97f5394ce52cc745a3c449ae26223e7813d94a44
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 dphaener
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'MontageRails'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
Bundler::GemHelper.install_tasks
|
18
|
+
|
19
|
+
require 'rake/testtask'
|
20
|
+
|
21
|
+
Rake::TestTask.new(:test) do |t|
|
22
|
+
t.libs << 'lib'
|
23
|
+
t.libs << 'test'
|
24
|
+
t.pattern = 'test/**/*_test.rb'
|
25
|
+
t.verbose = false
|
26
|
+
end
|
27
|
+
|
28
|
+
task default: :test
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module MontageRails
|
2
|
+
class Base
|
3
|
+
class Column
|
4
|
+
TYPE_MAP = {
|
5
|
+
"integer" => Integer,
|
6
|
+
"float" => Float,
|
7
|
+
"text" => String,
|
8
|
+
"date" => Date,
|
9
|
+
"time" => Time,
|
10
|
+
"datetime" => DateTime,
|
11
|
+
"numeric" => Numeric
|
12
|
+
}
|
13
|
+
|
14
|
+
attr_accessor :name, :type, :required
|
15
|
+
|
16
|
+
alias_method :required?, :required
|
17
|
+
|
18
|
+
def initialize(name, type, required = false)
|
19
|
+
@name = name
|
20
|
+
@type = type
|
21
|
+
@required = required
|
22
|
+
end
|
23
|
+
|
24
|
+
def value_valid?(value)
|
25
|
+
!(required? && value.nil?)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Determines if the string value passed in is an integer
|
29
|
+
# Returns true or false
|
30
|
+
#
|
31
|
+
def is_i?(value)
|
32
|
+
/\A\d+\z/ =~ value
|
33
|
+
end
|
34
|
+
|
35
|
+
# Determines if the string value passed in is a float
|
36
|
+
# Returns true or false
|
37
|
+
#
|
38
|
+
def is_f?(value)
|
39
|
+
/\A\d+\.\d+\z/ =~ value
|
40
|
+
end
|
41
|
+
|
42
|
+
def coerce(value)
|
43
|
+
return value if value.is_a?(TYPE_MAP[type])
|
44
|
+
|
45
|
+
if is_i?(value)
|
46
|
+
coerce_to = Integer
|
47
|
+
elsif is_f?(value)
|
48
|
+
coerce_to = Float
|
49
|
+
else
|
50
|
+
coerce_to = TYPE_MAP[type]
|
51
|
+
end
|
52
|
+
|
53
|
+
Virtus::Attribute.build(coerce_to).coerce(value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,406 @@
|
|
1
|
+
require 'montage_rails/log_subscriber'
|
2
|
+
require 'montage_rails/relation'
|
3
|
+
require 'montage_rails/base/column'
|
4
|
+
require 'active_model'
|
5
|
+
require 'virtus'
|
6
|
+
|
7
|
+
module MontageRails
|
8
|
+
class Base
|
9
|
+
extend ActiveModel::Callbacks
|
10
|
+
include ActiveModel::Model
|
11
|
+
include Virtus.model
|
12
|
+
|
13
|
+
define_model_callbacks :save, :create
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# Delegates all of the relation methods to the class level object, so they can be called on the base class
|
17
|
+
#
|
18
|
+
delegate :limit, :offset, :order, :where, :first, to: :relation
|
19
|
+
|
20
|
+
# Delegate the connection to the base module for ease of reference
|
21
|
+
#
|
22
|
+
delegate :connection, :notify, to: MontageRails
|
23
|
+
|
24
|
+
cattr_accessor :table_name
|
25
|
+
|
26
|
+
# Define a new instance of the query cache
|
27
|
+
#
|
28
|
+
def cache
|
29
|
+
@cache ||= QueryCache.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# Hook into the Rails logger
|
33
|
+
#
|
34
|
+
def logger
|
35
|
+
@logger ||= Rails.logger
|
36
|
+
end
|
37
|
+
|
38
|
+
# Setup a class level instance of the MontageRails::Relation object
|
39
|
+
#
|
40
|
+
def relation
|
41
|
+
@relation = Relation.new(self)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Define a has_many relationship
|
45
|
+
#
|
46
|
+
def has_many(table)
|
47
|
+
class_eval do
|
48
|
+
define_method(table.to_s.tableize.to_sym) do
|
49
|
+
table.to_s.classify.constantize.where("#{self.class.table_name.demodulize.underscore.singularize.foreign_key} = #{id}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Define a belongs_to relationship
|
55
|
+
#
|
56
|
+
def belongs_to(table)
|
57
|
+
class_eval do
|
58
|
+
define_method(table.to_s.tableize.singularize.to_sym) do
|
59
|
+
table.to_s.classify.constantize.find_by_id(__send__(table.to_s.foreign_key))
|
60
|
+
end
|
61
|
+
|
62
|
+
define_method("#{table.to_s.tableize.singularize}=") do |record|
|
63
|
+
self.__send__("#{table.to_s.foreign_key}=", record.id)
|
64
|
+
self
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# The pluralized table name used in API requests
|
70
|
+
#
|
71
|
+
def table_name
|
72
|
+
self.name.demodulize.underscore.pluralize
|
73
|
+
end
|
74
|
+
|
75
|
+
# Redefine the table name
|
76
|
+
#
|
77
|
+
def set_table_name(value)
|
78
|
+
instance_eval do
|
79
|
+
define_singleton_method(:table_name) do
|
80
|
+
value
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
alias_method :table_name=, :set_table_name
|
86
|
+
|
87
|
+
# Returns an array of MontageRails::Base::Column's for the schema
|
88
|
+
#
|
89
|
+
def columns
|
90
|
+
@columns ||= [].tap do |ary|
|
91
|
+
response = connection.schema(table_name)
|
92
|
+
|
93
|
+
return [] unless response.schema.respond_to?(:fields)
|
94
|
+
|
95
|
+
ary << Column.new("id", "text", false)
|
96
|
+
ary << Column.new("created_at", "datetime", false)
|
97
|
+
ary << Column.new("updated_at", "datetime", false)
|
98
|
+
|
99
|
+
response.schema.fields.each do |field|
|
100
|
+
ary << Column.new(field["name"], field["datatype"], field["required"])
|
101
|
+
|
102
|
+
instance_eval do
|
103
|
+
define_singleton_method("find_by_#{field["name"]}") do |value|
|
104
|
+
where("#{field["name"]} = '#{value}'").first
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Fetch all the documents
|
112
|
+
#
|
113
|
+
def all
|
114
|
+
relation.to_a
|
115
|
+
end
|
116
|
+
|
117
|
+
# Find a record by the id
|
118
|
+
#
|
119
|
+
def find_by_id(value)
|
120
|
+
response = cache.get_or_set_query(self, value) { connection.document(table_name, value) }
|
121
|
+
|
122
|
+
if response.success?
|
123
|
+
new(response.document.items.merge(persisted: true))
|
124
|
+
else
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
alias_method :find, :find_by_id
|
130
|
+
|
131
|
+
# Find the record using the given params, or initialize a new one with those params
|
132
|
+
#
|
133
|
+
def find_or_initialize_by(params = {})
|
134
|
+
return nil if params.empty?
|
135
|
+
|
136
|
+
query = relation.where(params)
|
137
|
+
|
138
|
+
response = cache.get_or_set_query(self, query) { connection.documents(table_name, query) }
|
139
|
+
|
140
|
+
if response.success? && response.documents.any?
|
141
|
+
new(attributes_from_response(response).merge(persisted: true))
|
142
|
+
else
|
143
|
+
new(params)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Returns an array of the column names for the table
|
148
|
+
#
|
149
|
+
def column_names
|
150
|
+
columns.map { |c| c.name }
|
151
|
+
end
|
152
|
+
|
153
|
+
# Initialize and save a new instance of the object
|
154
|
+
#
|
155
|
+
def create(params = {})
|
156
|
+
new(params).save
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns a string like 'Post id:integer, title:string, body:text'
|
160
|
+
#
|
161
|
+
def inspect
|
162
|
+
if self == Base
|
163
|
+
super
|
164
|
+
else
|
165
|
+
attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
|
166
|
+
"#{super}(#{attr_list})"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def method_missing(method_name, *args, &block)
|
171
|
+
__send__(:columns)
|
172
|
+
|
173
|
+
if respond_to?(method_name.to_sym)
|
174
|
+
__send__(method_name.to_sym, *args)
|
175
|
+
else
|
176
|
+
super(method_name, *args, &block)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def respond_to_missing?(method_name, include_private = false)
|
181
|
+
__send__(:column_names).include?(method_name.to_s.split("_").first) || super(method_name, include_private)
|
182
|
+
end
|
183
|
+
|
184
|
+
def attributes_from_response(response)
|
185
|
+
case response.members
|
186
|
+
when Montage::Documents then response.documents.first.attributes.merge(persisted: true)
|
187
|
+
when Montage::Document then response.document.attributes.merge(persisted: true)
|
188
|
+
when Montage::Errors then raise MontageAPIError, "There was an error with the Montage API: #{response.errors.attributes}"
|
189
|
+
when Montage::Error then raise MontageAPIError, "There was an error with the Montage API: #{response.error.attributes}"
|
190
|
+
else raise MontageAPIError, "There was an error with the Montage API, please try again."
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
attr_accessor :persisted
|
196
|
+
|
197
|
+
alias_method :persisted?, :persisted
|
198
|
+
|
199
|
+
delegate :connection, :notify, to: MontageRails
|
200
|
+
delegate :attributes_from_response, to: "self.class"
|
201
|
+
|
202
|
+
def initialize(params = {})
|
203
|
+
initialize_columns
|
204
|
+
@persisted = params[:persisted] ? params[:persisted] : false
|
205
|
+
@current_method = "Load"
|
206
|
+
super(params)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Save the record to the database
|
210
|
+
#
|
211
|
+
# Will return nil if the attributes are not valid
|
212
|
+
#
|
213
|
+
# Upon successful creation or update, will return an instance of self, otherwise returns nil
|
214
|
+
#
|
215
|
+
def save
|
216
|
+
run_callbacks :save do
|
217
|
+
return nil unless attributes_valid?
|
218
|
+
|
219
|
+
if persisted?
|
220
|
+
@current_method = "Update"
|
221
|
+
|
222
|
+
response = notify(self) do
|
223
|
+
connection.create_or_update_documents(self.class.table_name, [updateable_attributes(true)])
|
224
|
+
end
|
225
|
+
else
|
226
|
+
@current_method = "Create"
|
227
|
+
|
228
|
+
response = notify(self) do
|
229
|
+
connection.create_or_update_documents(self.class.table_name, [updateable_attributes(false)])
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
if response.success?
|
234
|
+
if persisted?
|
235
|
+
initialize(attributes_from_response(response))
|
236
|
+
else
|
237
|
+
run_callbacks :create do
|
238
|
+
initialize(attributes_from_response(response))
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
self
|
243
|
+
else
|
244
|
+
response
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# The bang method for save, which will raise an exception if saving is not successful
|
250
|
+
#
|
251
|
+
def save!
|
252
|
+
unless save
|
253
|
+
raise MontageAPIError, response.errors.attributes
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Update the given attributes for the document
|
258
|
+
#
|
259
|
+
# Returns false if the given attributes aren't valid
|
260
|
+
#
|
261
|
+
# Returns a copy of self if updating is successful
|
262
|
+
#
|
263
|
+
def update_attributes(params)
|
264
|
+
old_attributes = attributes.clone
|
265
|
+
|
266
|
+
params.each do |key, value|
|
267
|
+
if respond_to?(key.to_sym)
|
268
|
+
coerced_value = column_for(key.to_s).coerce(value)
|
269
|
+
send("#{key}=", coerced_value)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
return self if old_attributes == attributes
|
274
|
+
|
275
|
+
if attributes_valid?
|
276
|
+
@current_method = id.nil? ? "Create" : "Update"
|
277
|
+
|
278
|
+
response = notify(self) do
|
279
|
+
connection.create_or_update_documents(self.class.table_name, [updateable_attributes(!id.nil?)])
|
280
|
+
end
|
281
|
+
|
282
|
+
initialize(attributes_from_response(response))
|
283
|
+
@persisted = true
|
284
|
+
self
|
285
|
+
else
|
286
|
+
initialize(old_attributes)
|
287
|
+
false
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# Destroy the copy of this record from the database
|
292
|
+
#
|
293
|
+
def destroy
|
294
|
+
@current_method = "Delete"
|
295
|
+
notify(self) { connection.delete_document(self.class.table_name, id) }
|
296
|
+
|
297
|
+
@persisted = false
|
298
|
+
self
|
299
|
+
end
|
300
|
+
|
301
|
+
# Reload the current document
|
302
|
+
#
|
303
|
+
def reload
|
304
|
+
@current_method = "Load"
|
305
|
+
|
306
|
+
response = notify(self) do
|
307
|
+
connection.document(self.class.table_name, id)
|
308
|
+
end
|
309
|
+
|
310
|
+
initialize(attributes_from_response(response))
|
311
|
+
@persisted = true
|
312
|
+
self
|
313
|
+
end
|
314
|
+
|
315
|
+
def new_record?
|
316
|
+
!persisted?
|
317
|
+
end
|
318
|
+
|
319
|
+
# Returns the Column class instance for the attribute passed in
|
320
|
+
#
|
321
|
+
def column_for(name)
|
322
|
+
self.class.columns.select { |column| column.name == name }.first
|
323
|
+
end
|
324
|
+
|
325
|
+
# Performs a check to ensure that required columns have a value
|
326
|
+
#
|
327
|
+
def attributes_valid?
|
328
|
+
attributes.each do |key, value|
|
329
|
+
next unless column_class = column_for(key.to_s)
|
330
|
+
return false unless column_class.value_valid?(value)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# The attributes used to update the document
|
335
|
+
#
|
336
|
+
def updateable_attributes(include_id = false)
|
337
|
+
include_id ? attributes.except(:created_at, :updated_at) : attributes.except(:created_at, :updated_at, :id)
|
338
|
+
end
|
339
|
+
|
340
|
+
# Required for notifications to work, returns a payload suitable
|
341
|
+
# for the log subscriber
|
342
|
+
#
|
343
|
+
def payload
|
344
|
+
{
|
345
|
+
reql: reql_payload[@current_method],
|
346
|
+
name: "#{self.class.name} #{@current_method}"
|
347
|
+
}
|
348
|
+
end
|
349
|
+
|
350
|
+
# Returns an <tt>#inspect</tt>-like string for the value of the
|
351
|
+
# attribute +attr_name+. String attributes are elided after 50
|
352
|
+
# characters, and Date and Time attributes are returned in the
|
353
|
+
# <tt>:db</tt> format. Other attributes return the value of
|
354
|
+
# <tt>#inspect</tt> without modification.
|
355
|
+
#
|
356
|
+
# person = Person.create!(:name => "David Heinemeier Hansson " * 3)
|
357
|
+
#
|
358
|
+
# person.attribute_for_inspect(:name)
|
359
|
+
# # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
|
360
|
+
#
|
361
|
+
# person.attribute_for_inspect(:created_at)
|
362
|
+
# # => '"2009-01-12 04:48:57"'
|
363
|
+
#
|
364
|
+
def attribute_for_inspect(attr_name)
|
365
|
+
value = attributes[attr_name]
|
366
|
+
|
367
|
+
if value.is_a?(String) && value.length > 50
|
368
|
+
"#{value[0..50]}...".inspect
|
369
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
370
|
+
%("#{value.to_s(:db)}")
|
371
|
+
else
|
372
|
+
value.inspect
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
# Returns the contents of the record as a nicely formatted string.
|
377
|
+
#
|
378
|
+
def inspect
|
379
|
+
attributes_as_nice_string = self.class.column_names.collect { |name|
|
380
|
+
if attributes[name.to_sym] || new_record?
|
381
|
+
"#{name}: #{attribute_for_inspect(name.to_sym)}"
|
382
|
+
end
|
383
|
+
}.compact.join(", ")
|
384
|
+
"#<#{self.class} #{attributes_as_nice_string}>"
|
385
|
+
end
|
386
|
+
|
387
|
+
private
|
388
|
+
|
389
|
+
|
390
|
+
def initialize_columns
|
391
|
+
self.class.columns.each do |column|
|
392
|
+
self.class.__send__(:attribute, column.name.to_sym, Column::TYPE_MAP[column.type])
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def reql_payload
|
397
|
+
{
|
398
|
+
"Load" => id,
|
399
|
+
"Update" => "#{id}: #{updateable_attributes(true)}",
|
400
|
+
"Create" => updateable_attributes,
|
401
|
+
"Delete" => id,
|
402
|
+
"Save" => updateable_attributes
|
403
|
+
}
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module MontageRails
|
2
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
def self.runtime=(value)
|
4
|
+
Thread.current["montage_rails_reql_runtime"] = value
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.runtime
|
8
|
+
Thread.current["montage_rails_reql_runtime"] ||= 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.reset_runtime
|
12
|
+
rt, self.runtime = runtime, 0
|
13
|
+
rt
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
super
|
18
|
+
@odd_or_even = false
|
19
|
+
end
|
20
|
+
|
21
|
+
def reql(event)
|
22
|
+
self.class.runtime += event.duration
|
23
|
+
return unless logger.debug?
|
24
|
+
|
25
|
+
name = '%s (%.1fms)' % [event.payload[:name], event.duration]
|
26
|
+
reql = event.payload[:reql]
|
27
|
+
|
28
|
+
if odd?
|
29
|
+
name = color(name, CYAN, true)
|
30
|
+
reql = color(reql, nil, true)
|
31
|
+
else
|
32
|
+
name = color(name, MAGENTA, true)
|
33
|
+
end
|
34
|
+
|
35
|
+
debug " #{name} #{reql}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def odd?
|
39
|
+
@odd_or_even = !@odd_or_even
|
40
|
+
end
|
41
|
+
|
42
|
+
def logger
|
43
|
+
MontageRails::Base.logger
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
MontageRails::LogSubscriber.attach_to :montage_rails
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module MontageRails
|
2
|
+
class QueryCache
|
3
|
+
attr_reader :cache
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@cache = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_or_set_query(klass, query)
|
10
|
+
cached = cache.keys.include?("#{klass}/#{query}")
|
11
|
+
ActiveSupport::Notifications.instrument("reql.montage_rails", notification_payload(query, klass, cached)) do
|
12
|
+
if cached
|
13
|
+
cache["#{klass}/#{query}"]
|
14
|
+
else
|
15
|
+
response = yield
|
16
|
+
cache["#{klass}/#{query}"] = response
|
17
|
+
response
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Clear the entire query cache
|
23
|
+
#
|
24
|
+
def clear
|
25
|
+
@cache = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Remove a certain key from the cache
|
29
|
+
# Returns the removed value, or nil if nothin was found
|
30
|
+
#
|
31
|
+
def remove(key)
|
32
|
+
cache.delete(key)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def notification_payload(query, klass, cached = false)
|
38
|
+
{
|
39
|
+
reql: query,
|
40
|
+
name: cached ? "#{klass} Load [CACHE]" : "#{klass} Load"
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
File without changes
|