montage_rails 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|