rgviz-rails 0.43 → 0.44
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +101 -0
- data/lib/rgviz_rails.rb +1 -0
- data/lib/rgviz_rails/executor.rb +10 -26
- data/{rails → lib/rgviz_rails}/init.rb +31 -19
- data/lib/rgviz_rails/view_helper.rb +86 -82
- data/spec/blueprints.rb +0 -7
- data/spec/rgviz/executor_spec.rb +2 -11
- data/spec/spec_helper.rb +0 -10
- metadata +20 -12
- data/README +0 -243
- data/spec/models/pet.rb +0 -3
data/README.rdoc
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
== Rgviz-rails
|
2
|
+
|
3
|
+
This library makes it easy to implement a Visualization data source so that you can easily chart or visualize your data from ActiveRecord[http://ar.rubyonrails.org/] models. The library implements the {Google Visualization API wire protocol}[http://code.google.com/apis/visualization/documentation/dev/implementing_data_source.html].
|
4
|
+
|
5
|
+
It also allows you to {render the visualizations in a view template}[https://github.com/asterite/rgviz-rails/wiki/Showing-a-visualization-in-a-view] in a very simple but powerful way.
|
6
|
+
|
7
|
+
This library is built on top of rgviz[https://github.com/asterite/rgviz].
|
8
|
+
|
9
|
+
=== Installation
|
10
|
+
|
11
|
+
gem install rgviz-rails
|
12
|
+
|
13
|
+
In your environment.rb
|
14
|
+
|
15
|
+
config.gem "rgviz-rails", :lib => 'rgviz_rails'
|
16
|
+
|
17
|
+
=== Usage
|
18
|
+
|
19
|
+
To make a method in your controller be a visualization API endpoint:
|
20
|
+
|
21
|
+
class VizController < ApplicationController
|
22
|
+
|
23
|
+
def person
|
24
|
+
# Person is an ActiveRecord::Base class
|
25
|
+
render :rgviz => Person
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
So for example if +Person+ has +name+ and +age+, pointing your browser to:
|
31
|
+
|
32
|
+
http://localhost:3000/viz/person?select name where age > 20 limit 5
|
33
|
+
|
34
|
+
would render the necessary javascript code that implements the Google Visualization API wire protocol.
|
35
|
+
|
36
|
+
=== Extensions
|
37
|
+
|
38
|
+
To enable the extensions defined by rgviz you need to specify it in the render method:
|
39
|
+
|
40
|
+
render :rgviz => Person, :extensions => true
|
41
|
+
|
42
|
+
=== Associations
|
43
|
+
|
44
|
+
If you want to filter, order by or group by columns that are in a model's association you can use underscores. This is better understood with an example:
|
45
|
+
|
46
|
+
class Person < ActiveRecord::Base
|
47
|
+
belongs_to :city
|
48
|
+
end
|
49
|
+
|
50
|
+
class City < ActiveRecord::Base
|
51
|
+
belongs_to :country
|
52
|
+
end
|
53
|
+
|
54
|
+
class Country < ActiveRecord::Base
|
55
|
+
end
|
56
|
+
|
57
|
+
To select the name of the city each person belongs to:
|
58
|
+
|
59
|
+
select city_name
|
60
|
+
|
61
|
+
To select the name of the country of the city each person belongs to:
|
62
|
+
|
63
|
+
select city_country_name
|
64
|
+
|
65
|
+
A slightly more complex example:
|
66
|
+
|
67
|
+
select avg(age) where city_country_name = 'Argentina' group by city_name
|
68
|
+
|
69
|
+
The library will make it in just one query, writing all the SQL joins for you.
|
70
|
+
|
71
|
+
=== Extra conditions
|
72
|
+
|
73
|
+
Sometimes you want to limit your results the query will work with. You can do it like this:
|
74
|
+
|
75
|
+
render :rgviz => Person, :conditions => ['age > ?', 20]
|
76
|
+
|
77
|
+
Or also:
|
78
|
+
|
79
|
+
render :rgviz => Person, :conditions => 'age > 20'
|
80
|
+
|
81
|
+
=== Preprocessing
|
82
|
+
|
83
|
+
If you need to tweak a result before returning it, just include a block:
|
84
|
+
|
85
|
+
render :rgviz => Person do |table|
|
86
|
+
# Modify the Rgviz::Table object
|
87
|
+
end
|
88
|
+
|
89
|
+
=== Showing a visualization in a view
|
90
|
+
|
91
|
+
You can invoke the rgviz method in your views. {Read more about this}[https://github.com/asterite/rgviz-rails/wiki/Showing-a-visualization-in-a-view].
|
92
|
+
|
93
|
+
You can always do it the {old way}[http://code.google.com/apis/visualization/documentation/using_overview.html]. {Read more about this}
|
94
|
+
|
95
|
+
=== Current Limitations
|
96
|
+
* The *format* clause is ignored (If someone knows of a working icu library for ruby, please tell me)
|
97
|
+
* Only supports MySQL, PostgreSQL and SQLite adapters
|
98
|
+
* These scalar functions are not supported for SQLite: *millisecond*, *quarter*
|
99
|
+
* These scalar functions are not supported for MySQL: *millisecond*
|
100
|
+
* The function *toDate* doesn't accept a number as its argument
|
101
|
+
* The *tsv* output format is not supported
|
data/lib/rgviz_rails.rb
CHANGED
data/lib/rgviz_rails/executor.rb
CHANGED
@@ -517,32 +517,16 @@ module Rgviz
|
|
517
517
|
col = klass.send(:columns).select{|x| x.name == name}.first
|
518
518
|
return [klass, col, joins] if col
|
519
519
|
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
return nil
|
531
|
-
end
|
532
|
-
|
533
|
-
before = name[0 ... idx]
|
534
|
-
new_name = name[idx + 1 .. -1]
|
535
|
-
|
536
|
-
assoc = klass.send :reflect_on_association, before.to_sym
|
537
|
-
if assoc
|
538
|
-
name = new_name
|
539
|
-
klass = assoc.klass
|
540
|
-
joins << assoc
|
541
|
-
break
|
542
|
-
end
|
543
|
-
|
544
|
-
offset = idx + 1
|
545
|
-
end
|
520
|
+
idx = name.index '_'
|
521
|
+
return nil if not idx
|
522
|
+
|
523
|
+
before = name[0 ... idx]
|
524
|
+
name = name[idx + 1 .. -1]
|
525
|
+
|
526
|
+
assoc = klass.send :reflect_on_association, before.to_sym
|
527
|
+
raise "Unknown association #{before}" unless assoc
|
528
|
+
klass = assoc.klass
|
529
|
+
joins << assoc
|
546
530
|
end
|
547
531
|
end
|
548
532
|
|
@@ -2,63 +2,75 @@ require "rgviz_rails/view_helper"
|
|
2
2
|
# includes the view helper to ActionView::Base
|
3
3
|
ActionView::Base.send(:include, Rgviz::ViewHelper)
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def render(*args, &block)
|
5
|
+
def _define_rgviz_class
|
6
|
+
::ActionController::Base.module_eval do
|
7
|
+
def render_with_rgviz(*args, &block)
|
9
8
|
if args.length == 1 && args[0].kind_of?(Hash)
|
10
|
-
hash = args.first
|
9
|
+
hash = args.first
|
11
10
|
case hash[:rgviz]
|
12
|
-
when nil then
|
11
|
+
when nil then render_without_rgviz *args, &block
|
13
12
|
else
|
14
13
|
model = hash[:rgviz]
|
15
14
|
conditions = hash[:conditions]
|
16
15
|
extensions = hash[:extensions]
|
17
16
|
query = params[:tq]
|
18
17
|
tqx = params[:tqx] || ''
|
19
|
-
|
18
|
+
|
20
19
|
tqx = Rgviz::Tqx.parse(tqx)
|
21
|
-
|
20
|
+
|
22
21
|
begin
|
23
22
|
executor = Rgviz::Executor.new model, query
|
24
23
|
options = {}
|
25
24
|
options[:conditions] = conditions if conditions
|
26
25
|
options[:extensions] = extensions if extensions
|
27
26
|
table = executor.execute options
|
28
|
-
|
27
|
+
|
29
28
|
yield table if block_given?
|
30
|
-
|
29
|
+
|
31
30
|
case tqx['out']
|
32
31
|
when 'json'
|
33
|
-
|
32
|
+
render_without_rgviz :text => Rgviz::JsRenderer.render(table, tqx)
|
34
33
|
when 'html'
|
35
|
-
|
34
|
+
render_without_rgviz :text => Rgviz::HtmlRenderer.render(table)
|
36
35
|
when 'csv'
|
37
36
|
csv_output = Rgviz::CsvRenderer.render(table)
|
38
37
|
if tqx['outFileName']
|
39
38
|
send_data csv_output, :filename => tqx['outFileName'], :type => 'text/csv'
|
40
39
|
else
|
41
|
-
|
40
|
+
render_without_rgviz :text => csv_output
|
42
41
|
end
|
43
42
|
else
|
44
|
-
|
43
|
+
render_without_rgviz :text => Rgviz::JsRenderer.render_error('not_supported', "Unsupported output type: #{out}", tqx)
|
45
44
|
end
|
46
45
|
rescue ParseException => e
|
47
46
|
case tqx['out']
|
48
47
|
when 'json'
|
49
|
-
|
48
|
+
render_without_rgviz :text => Rgviz::JsRenderer.render_error('invalid_query', e.message, tqx)
|
50
49
|
when 'html'
|
51
|
-
|
50
|
+
render_without_rgviz :text => "<b>Error:</b> #{e.message}"
|
52
51
|
when 'csv'
|
53
|
-
|
52
|
+
render_without_rgviz :text => "Error: #{e.message}"
|
54
53
|
else
|
55
|
-
|
54
|
+
render_without_rgviz :text => "<b>Unsupported output type:</b> #{out}"
|
56
55
|
end
|
57
56
|
end
|
58
57
|
end
|
59
58
|
else
|
60
|
-
|
59
|
+
render_without_rgviz *args, &block
|
61
60
|
end
|
62
61
|
end
|
62
|
+
alias_method_chain :render, :rgviz
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if Rails::VERSION::MAJOR == 2
|
67
|
+
config.after_initialize do
|
68
|
+
_define_rgviz_class
|
69
|
+
end
|
70
|
+
else
|
71
|
+
class Railtie < Rails::Railtie
|
72
|
+
initializer "define rgviz class" do
|
73
|
+
_define_rgviz_class
|
74
|
+
end
|
63
75
|
end
|
64
76
|
end
|
@@ -6,7 +6,7 @@ module Rgviz
|
|
6
6
|
i = 0
|
7
7
|
opts.each do |key, value|
|
8
8
|
key = key.to_s.gsub('"', '\"')
|
9
|
-
|
9
|
+
|
10
10
|
@s << ',' if i > 0
|
11
11
|
@s << "\"#{key}\":"
|
12
12
|
if special_keys.include?(key) || !value.kind_of?(String)
|
@@ -19,9 +19,9 @@ module Rgviz
|
|
19
19
|
end
|
20
20
|
@s << '}'
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
options = options.with_indifferent_access
|
24
|
-
|
24
|
+
|
25
25
|
id = options[:id]
|
26
26
|
kind = options[:kind]
|
27
27
|
url = options[:url]
|
@@ -30,74 +30,74 @@ module Rgviz
|
|
30
30
|
html = options[:html] || {}
|
31
31
|
hidden = options[:hidden]
|
32
32
|
extensions = options[:extensions]
|
33
|
-
|
33
|
+
|
34
34
|
rgviz_events, google_events = events.partition{|x| x.to_s.start_with? 'rgviz'}
|
35
35
|
rgviz_events = rgviz_events.inject(Hash.new){|h, y| h[y[0]] = y[1]; h}
|
36
36
|
rgviz_events = rgviz_events.with_indifferent_access
|
37
|
-
|
37
|
+
|
38
38
|
html_prefix = (options[:html_prefix] || 'html').to_s
|
39
39
|
js_prefix = (options[:js_prefix] || 'js').to_s
|
40
40
|
param_prefix = (options[:param_prefix] || 'param').to_s
|
41
|
-
|
41
|
+
|
42
42
|
html_prefix += '_'
|
43
43
|
js_prefix += '_'
|
44
44
|
param_prefix += '_'
|
45
|
-
|
45
|
+
|
46
46
|
debug = options[:debug]
|
47
47
|
opts = options[:options] || {}
|
48
48
|
opts[:width] = 640 unless opts[:width]
|
49
49
|
opts[:height] = 480 unless opts[:height]
|
50
|
-
|
50
|
+
|
51
51
|
params = []
|
52
52
|
uses_rgviz_get_value = false
|
53
53
|
uses_rgviz_append = false
|
54
|
-
|
54
|
+
|
55
55
|
visitor = MagicNamesVisitor.new(html_prefix, js_prefix, param_prefix)
|
56
|
-
|
56
|
+
|
57
57
|
special_keys = []
|
58
|
-
|
58
|
+
|
59
59
|
opts.each do |key, value|
|
60
60
|
next unless value.kind_of?(String)
|
61
|
-
|
61
|
+
|
62
62
|
source = visitor.get_source(value, false)
|
63
63
|
next unless source[:source]
|
64
|
-
|
64
|
+
|
65
65
|
special_keys << key
|
66
|
-
|
66
|
+
|
67
67
|
case source[:source]
|
68
68
|
when :html
|
69
|
-
opts[key] = "rgviz_get_value('#{source[:id]}')"
|
70
|
-
uses_rgviz_get_value = true
|
69
|
+
opts[key] = "rgviz_get_value('#{source[:id]}')"
|
70
|
+
uses_rgviz_get_value = true
|
71
71
|
when :js
|
72
72
|
opts[key] = "#{source[:id]}()"
|
73
73
|
when :param
|
74
74
|
opts[key] = "param_#{source[:id]}"
|
75
75
|
params << source[:id].to_i unless params.include?(source[:id])
|
76
|
-
end
|
76
|
+
end
|
77
77
|
end
|
78
|
-
|
78
|
+
|
79
79
|
opts = opts_to_json(opts, special_keys)
|
80
|
-
|
80
|
+
|
81
81
|
raise "Must specify an :id" unless id
|
82
82
|
raise "Must specify a :kind" unless kind
|
83
83
|
raise "Must specify a :url" unless url
|
84
|
-
|
84
|
+
|
85
85
|
url = url_for url
|
86
|
-
|
86
|
+
|
87
87
|
# Parse the query
|
88
88
|
query = Parser.parse query, :extensions => extensions
|
89
|
-
|
89
|
+
|
90
90
|
# And replace the html_ and javascript_ magic names
|
91
91
|
query.accept visitor
|
92
92
|
query_builder = visitor.query_builder
|
93
93
|
query_builder_var = visitor.query_builder_var
|
94
|
-
|
94
|
+
|
95
95
|
uses_rgviz_get_value |= visitor.uses_rgviz_get_value?
|
96
96
|
uses_rgviz_append |= visitor.uses_rgviz_append?
|
97
|
-
|
97
|
+
|
98
98
|
visitor.params.each{|p| params << p unless params.include?(p)}
|
99
99
|
params = params.sort!.map{|i| "param_#{i}"}
|
100
|
-
|
100
|
+
|
101
101
|
out = ''
|
102
102
|
|
103
103
|
# Output the google jsapi tag the first time
|
@@ -107,15 +107,15 @@ module Rgviz
|
|
107
107
|
end
|
108
108
|
# Now the real script
|
109
109
|
out << "<script type=\"text/javascript\">\n"
|
110
|
-
|
110
|
+
|
111
111
|
# Define a function to get the value of an html element
|
112
|
-
if uses_rgviz_get_value && !@defined_rgviz_get_value
|
112
|
+
if uses_rgviz_get_value && !@defined_rgviz_get_value
|
113
113
|
out << "function rgviz_get_value(id) {\n"
|
114
114
|
out << "var e = document.getElementById(id);\n"
|
115
115
|
out << "var n = e.tagName.toLowerCase();\n"
|
116
116
|
out << "var s = null;\n"
|
117
117
|
out << "if (n == 'select' && e.multiple) {\n"
|
118
|
-
out << "var s = [];\n"
|
118
|
+
out << "var s = [];\n"
|
119
119
|
out << "var o = e.options;\n"
|
120
120
|
out << "for(var i = 0; i < o.length; i++)\n"
|
121
121
|
out << "if (o[i].selected) s.push(o[i].value);\n"
|
@@ -128,7 +128,7 @@ module Rgviz
|
|
128
128
|
out << "}\n"
|
129
129
|
@defined_rgviz_get_value = true
|
130
130
|
end
|
131
|
-
|
131
|
+
|
132
132
|
# Define a function to append the value of a magic something
|
133
133
|
if uses_rgviz_append && !@defined_rgviz_append
|
134
134
|
out << "function rgviz_append(s, b, a) {\n"
|
@@ -147,27 +147,27 @@ module Rgviz
|
|
147
147
|
out << "}\n"
|
148
148
|
@defined_rgviz_append = true
|
149
149
|
end
|
150
|
-
|
150
|
+
|
151
151
|
# Load visualizations and the package, if not already loaded
|
152
|
-
pack = kind.downcase
|
152
|
+
pack = kind.downcase
|
153
153
|
@packages ||= []
|
154
154
|
unless @packages.include?(pack)
|
155
155
|
out << "google.load(\"visualization\", \"1\", {'packages':['#{pack}']});\n"
|
156
156
|
@packages << pack
|
157
157
|
end
|
158
|
-
|
158
|
+
|
159
159
|
callback = "rgviz_draw_#{id}"
|
160
|
-
|
160
|
+
|
161
161
|
# Set the callback if the function doesn't have params and if the
|
162
162
|
# user didn't request to hide the visualization
|
163
163
|
if !hidden && params.empty?
|
164
164
|
out << "google.setOnLoadCallback(#{callback});\n"
|
165
165
|
end
|
166
|
-
|
166
|
+
|
167
167
|
# Define the visualization var and data
|
168
168
|
out << "var rgviz_#{id} = null;\n"
|
169
169
|
out << "var rgviz_#{id}_data = null;\n"
|
170
|
-
|
170
|
+
|
171
171
|
# And define the callback
|
172
172
|
out << "function #{callback}(#{params.join(', ')}) {\n"
|
173
173
|
out << "#{rgviz_events[:rgviz_start]}('#{id}');\n" if rgviz_events[:rgviz_start]
|
@@ -185,24 +185,28 @@ module Rgviz
|
|
185
185
|
out << "#{rgviz_events[:rgviz_end]}('#{id}');\n" if rgviz_events[:rgviz_end]
|
186
186
|
out << "});\n"
|
187
187
|
out << "}\n"
|
188
|
-
|
188
|
+
|
189
189
|
out << "</script>\n"
|
190
|
-
|
190
|
+
|
191
191
|
# Write the div
|
192
192
|
out << "<div id=\"#{id}\""
|
193
193
|
html.each do |key, value|
|
194
194
|
out << " #{key}=\"#{h value}\""
|
195
195
|
end
|
196
196
|
out << "></div>\n"
|
197
|
-
|
197
|
+
|
198
198
|
@first_time = 0
|
199
|
-
|
200
|
-
|
199
|
+
|
200
|
+
if defined? :raw
|
201
|
+
raw out
|
202
|
+
else
|
203
|
+
out
|
204
|
+
end
|
201
205
|
end
|
202
|
-
|
206
|
+
|
203
207
|
module_function :rgviz
|
204
208
|
end
|
205
|
-
|
209
|
+
|
206
210
|
class MagicNamesVisitor < Visitor
|
207
211
|
def initialize(html_prefix, js_prefix, param_prefix)
|
208
212
|
@html_prefix = html_prefix
|
@@ -211,27 +215,27 @@ module Rgviz
|
|
211
215
|
@s = ''
|
212
216
|
@params = []
|
213
217
|
end
|
214
|
-
|
218
|
+
|
215
219
|
def query_builder
|
216
220
|
@s.strip
|
217
221
|
end
|
218
|
-
|
222
|
+
|
219
223
|
def query_builder_var
|
220
224
|
'q'
|
221
225
|
end
|
222
|
-
|
226
|
+
|
223
227
|
def params
|
224
228
|
@params
|
225
229
|
end
|
226
|
-
|
230
|
+
|
227
231
|
def uses_rgviz_get_value?
|
228
232
|
@uses_rgviz_get_value
|
229
233
|
end
|
230
|
-
|
234
|
+
|
231
235
|
def uses_rgviz_append?
|
232
236
|
@uses_rgviz_append
|
233
237
|
end
|
234
|
-
|
238
|
+
|
235
239
|
def visit_query(node)
|
236
240
|
@s << "var q = '"
|
237
241
|
node.select.accept self if node.select
|
@@ -241,20 +245,20 @@ module Rgviz
|
|
241
245
|
node.order_by.accept self if node.order_by
|
242
246
|
@s << "limit #{node.limit} " if node.limit
|
243
247
|
@s << "offset #{node.offset} " if node.offset
|
244
|
-
if node.labels
|
248
|
+
if node.labels
|
245
249
|
@s << "label "
|
246
250
|
node.labels.each_with_index do |l, i|
|
247
|
-
@s << ', ' if i > 0
|
251
|
+
@s << ', ' if i > 0
|
248
252
|
l.accept self
|
249
253
|
end
|
250
|
-
end
|
254
|
+
end
|
251
255
|
if node.formats
|
252
256
|
@s << "format "
|
253
257
|
node.formats.each_with_index do |f, i|
|
254
|
-
@s << ', ' if i > 0
|
258
|
+
@s << ', ' if i > 0
|
255
259
|
f.accept self
|
256
260
|
end
|
257
|
-
end
|
261
|
+
end
|
258
262
|
if node.options
|
259
263
|
@s << "options "
|
260
264
|
@s << "no_values " if node.options.no_values
|
@@ -263,35 +267,35 @@ module Rgviz
|
|
263
267
|
@s << "';\n"
|
264
268
|
false
|
265
269
|
end
|
266
|
-
|
270
|
+
|
267
271
|
def visit_select(node)
|
268
272
|
@s << "select ";
|
269
273
|
print_columns node
|
270
274
|
@s << " "
|
271
275
|
false
|
272
276
|
end
|
273
|
-
|
277
|
+
|
274
278
|
def visit_where(node)
|
275
279
|
@s << "where "
|
276
280
|
node.expression.accept self
|
277
281
|
@s << " "
|
278
282
|
false
|
279
283
|
end
|
280
|
-
|
284
|
+
|
281
285
|
def visit_group_by(node)
|
282
286
|
@s << "group by "
|
283
287
|
print_columns node
|
284
288
|
@s << " "
|
285
289
|
false
|
286
290
|
end
|
287
|
-
|
291
|
+
|
288
292
|
def visit_pivot(node)
|
289
293
|
@s << "pivot "
|
290
294
|
print_columns node
|
291
295
|
@s << " "
|
292
296
|
false
|
293
297
|
end
|
294
|
-
|
298
|
+
|
295
299
|
def visit_order_by(node)
|
296
300
|
@s << "order by "
|
297
301
|
node.sorts.each_with_index do |s, i|
|
@@ -303,7 +307,7 @@ module Rgviz
|
|
303
307
|
@s << " "
|
304
308
|
false
|
305
309
|
end
|
306
|
-
|
310
|
+
|
307
311
|
def visit_label(node)
|
308
312
|
node.column.accept self
|
309
313
|
@s << ' '
|
@@ -315,7 +319,7 @@ module Rgviz
|
|
315
319
|
end
|
316
320
|
false
|
317
321
|
end
|
318
|
-
|
322
|
+
|
319
323
|
def visit_format(node)
|
320
324
|
node.column.accept self
|
321
325
|
@s << ' '
|
@@ -326,7 +330,7 @@ module Rgviz
|
|
326
330
|
end
|
327
331
|
false
|
328
332
|
end
|
329
|
-
|
333
|
+
|
330
334
|
def visit_logical_expression(node)
|
331
335
|
@s += "("
|
332
336
|
node.operands.each_with_index do |operand, i|
|
@@ -336,9 +340,9 @@ module Rgviz
|
|
336
340
|
@s += ")"
|
337
341
|
false
|
338
342
|
end
|
339
|
-
|
343
|
+
|
340
344
|
def visit_binary_expression(node)
|
341
|
-
if node.operator == BinaryExpression::Eq
|
345
|
+
if node.operator == BinaryExpression::Eq
|
342
346
|
source = has_magic_name?(node.right)
|
343
347
|
if source
|
344
348
|
@s << "';\n"
|
@@ -346,7 +350,7 @@ module Rgviz
|
|
346
350
|
when :html
|
347
351
|
@s << "var s = rgviz_get_value('#{source[:id]}');\n"
|
348
352
|
append_selections node, source
|
349
|
-
|
353
|
+
|
350
354
|
@uses_rgviz_get_value = true
|
351
355
|
when :js
|
352
356
|
@s << "var s = #{source[:id]}();\n"
|
@@ -367,7 +371,7 @@ module Rgviz
|
|
367
371
|
node.right.accept self
|
368
372
|
false
|
369
373
|
end
|
370
|
-
|
374
|
+
|
371
375
|
def visit_unary_expression(node)
|
372
376
|
if node.operator == UnaryExpression::Not
|
373
377
|
@s << "not "
|
@@ -388,7 +392,7 @@ module Rgviz
|
|
388
392
|
append_before_source_type source[:type]
|
389
393
|
@s << " + rgviz_get_value('#{source[:id]}') + "
|
390
394
|
append_after_source_type source[:type]
|
391
|
-
|
395
|
+
|
392
396
|
@uses_rgviz_get_value = true
|
393
397
|
when :js
|
394
398
|
append_before_source_type source[:type]
|
@@ -401,35 +405,35 @@ module Rgviz
|
|
401
405
|
@params << source[:id].to_i unless @params.include?(source[:id])
|
402
406
|
end
|
403
407
|
end
|
404
|
-
|
408
|
+
|
405
409
|
def visit_number_column(node)
|
406
410
|
@s << node.value.to_s
|
407
411
|
end
|
408
|
-
|
412
|
+
|
409
413
|
def visit_string_column(node)
|
410
414
|
value = node.value.gsub('"', '\"')
|
411
415
|
@s << "\"#{value}\""
|
412
416
|
end
|
413
|
-
|
417
|
+
|
414
418
|
def visit_boolean_column(node)
|
415
419
|
@s << node.value.to_s
|
416
420
|
end
|
417
|
-
|
421
|
+
|
418
422
|
def visit_date_column(node)
|
419
423
|
@s << "date \"#{node.value.to_s}\""
|
420
424
|
end
|
421
|
-
|
425
|
+
|
422
426
|
def visit_date_time_column(node)
|
423
427
|
@s << "date \"#{node.value.strftime('%Y-%m-%d %H:%M:%S')}\""
|
424
428
|
end
|
425
|
-
|
429
|
+
|
426
430
|
def visit_time_of_day_column(node)
|
427
431
|
@s << "date \"#{node.value.strftime('%H:%M:%S')}\""
|
428
432
|
end
|
429
|
-
|
433
|
+
|
430
434
|
def visit_scalar_function_column(node)
|
431
435
|
case node.function
|
432
|
-
when ScalarFunctionColumn::Sum, ScalarFunctionColumn::Difference,
|
436
|
+
when ScalarFunctionColumn::Sum, ScalarFunctionColumn::Difference,
|
433
437
|
ScalarFunctionColumn::Product, ScalarFunctionColumn::Quotient
|
434
438
|
node.arguments[0].accept node
|
435
439
|
@s << " #{node.function} "
|
@@ -444,21 +448,21 @@ module Rgviz
|
|
444
448
|
end
|
445
449
|
false
|
446
450
|
end
|
447
|
-
|
451
|
+
|
448
452
|
def visit_aggregate_column(node)
|
449
453
|
@s << "#{node.function}("
|
450
454
|
node.argument.accept self
|
451
455
|
@s << ")"
|
452
456
|
false
|
453
457
|
end
|
454
|
-
|
458
|
+
|
455
459
|
def print_columns(node)
|
456
460
|
node.columns.each_with_index do |c, i|
|
457
461
|
@s << ', ' if i > 0
|
458
462
|
c.accept self
|
459
463
|
end
|
460
464
|
end
|
461
|
-
|
465
|
+
|
462
466
|
def get_source(name, include_type = true)
|
463
467
|
if name.start_with?(@html_prefix)
|
464
468
|
if include_type
|
@@ -482,7 +486,7 @@ module Rgviz
|
|
482
486
|
{}
|
483
487
|
end
|
484
488
|
end
|
485
|
-
|
489
|
+
|
486
490
|
def get_source_type(source, name)
|
487
491
|
if name.start_with?('number_')
|
488
492
|
{:source => source, :id => name[7 .. -1], :type => :number}
|
@@ -498,7 +502,7 @@ module Rgviz
|
|
498
502
|
{:source => source, :id => name, :type => :string}
|
499
503
|
end
|
500
504
|
end
|
501
|
-
|
505
|
+
|
502
506
|
def append_before_source_type(type)
|
503
507
|
case type
|
504
508
|
when :number
|
@@ -513,7 +517,7 @@ module Rgviz
|
|
513
517
|
@s << "timeofday \"'"
|
514
518
|
end
|
515
519
|
end
|
516
|
-
|
520
|
+
|
517
521
|
def append_after_source_type(type)
|
518
522
|
case type
|
519
523
|
when :number
|
@@ -522,7 +526,7 @@ module Rgviz
|
|
522
526
|
@s << "'\""
|
523
527
|
end
|
524
528
|
end
|
525
|
-
|
529
|
+
|
526
530
|
def append_selections(node, source)
|
527
531
|
@s << "q += rgviz_append(s, '";
|
528
532
|
node.left.accept self
|
@@ -533,7 +537,7 @@ module Rgviz
|
|
533
537
|
@s << "');\n"
|
534
538
|
@uses_rgviz_append = true
|
535
539
|
end
|
536
|
-
|
540
|
+
|
537
541
|
def has_magic_name?(node)
|
538
542
|
return false unless node.kind_of?(IdColumn)
|
539
543
|
source = get_source node.name
|
data/spec/blueprints.rb
CHANGED
data/spec/rgviz/executor_spec.rb
CHANGED
@@ -5,12 +5,11 @@ include Rgviz
|
|
5
5
|
|
6
6
|
describe Executor do
|
7
7
|
before :each do
|
8
|
-
[Person,
|
8
|
+
[Person, City, Country].each &:delete_all
|
9
9
|
end
|
10
10
|
|
11
11
|
def exec(query, options = {})
|
12
|
-
|
13
|
-
exec = Executor.new clazz, query
|
12
|
+
exec = Executor.new Person, query
|
14
13
|
exec.execute options
|
15
14
|
end
|
16
15
|
|
@@ -287,14 +286,6 @@ describe Executor do
|
|
287
286
|
|
288
287
|
it_processes_single_select_column "1 options no_values", 'c0', :number, nil, "1"
|
289
288
|
|
290
|
-
it_processes_single_select_column "1 where city is null", 'c0', :number, 1, "1" do
|
291
|
-
Person.make :city => nil
|
292
|
-
end
|
293
|
-
|
294
|
-
it_processes_single_select_column "the_city_name where the_city_name = 'foo'", 'the_city_name', :string, 'foo', "the_city_name", :class => Pet do
|
295
|
-
Pet.make :the_city => (City.make :name => 'foo')
|
296
|
-
end
|
297
|
-
|
298
289
|
it "processes pivot" do
|
299
290
|
Person.make :name => 'Eng', :birthday => '2000-01-12', :age => 1000
|
300
291
|
Person.make :name => 'Eng', :birthday => '2000-01-12', :age => 500
|
data/spec/spec_helper.rb
CHANGED
@@ -32,19 +32,9 @@ ActiveRecord::Schema.define do
|
|
32
32
|
t.datetime "updated_at"
|
33
33
|
t.integer "city_id"
|
34
34
|
end
|
35
|
-
|
36
|
-
create_table "pets", :force => true do |t|
|
37
|
-
t.string "name"
|
38
|
-
t.integer "age"
|
39
|
-
t.date "birthday"
|
40
|
-
t.datetime "created_at"
|
41
|
-
t.datetime "updated_at"
|
42
|
-
t.integer "city_id"
|
43
|
-
end
|
44
35
|
end
|
45
36
|
|
46
37
|
require File.dirname(__FILE__) + '/models/person'
|
47
|
-
require File.dirname(__FILE__) + '/models/pet'
|
48
38
|
require File.dirname(__FILE__) + '/models/city'
|
49
39
|
require File.dirname(__FILE__) + '/models/country'
|
50
40
|
|
metadata
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rgviz-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 93
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
8
|
-
-
|
9
|
-
version: "0.
|
7
|
+
- 44
|
8
|
+
version: "0.44"
|
10
9
|
platform: ruby
|
11
10
|
authors:
|
12
11
|
- Ary Borenszweig
|
@@ -14,10 +13,22 @@ autorequire:
|
|
14
13
|
bindir: bin
|
15
14
|
cert_chain: []
|
16
15
|
|
17
|
-
date: 2010-
|
16
|
+
date: 2010-12-11 00:00:00 -03:00
|
18
17
|
default_executable:
|
19
|
-
dependencies:
|
20
|
-
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: rgviz
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
version_requirements: *id001
|
21
32
|
description:
|
22
33
|
email: aborenszweig@manas.com.ar
|
23
34
|
executables: []
|
@@ -25,7 +36,7 @@ executables: []
|
|
25
36
|
extensions: []
|
26
37
|
|
27
38
|
extra_rdoc_files:
|
28
|
-
- README
|
39
|
+
- README.rdoc
|
29
40
|
files:
|
30
41
|
- lib/rgviz_rails.rb
|
31
42
|
- lib/rgviz_rails/executor.rb
|
@@ -35,16 +46,15 @@ files:
|
|
35
46
|
- lib/rgviz_rails/adapters/mysql_adapter.rb
|
36
47
|
- lib/rgviz_rails/adapters/postgresql_adapter.rb
|
37
48
|
- lib/rgviz_rails/adapters/sqlite_adapter.rb
|
38
|
-
-
|
49
|
+
- lib/rgviz_rails/init.rb
|
39
50
|
- spec/blueprints.rb
|
40
51
|
- spec/spec.opts
|
41
52
|
- spec/spec_helper.rb
|
42
53
|
- spec/models/city.rb
|
43
54
|
- spec/models/country.rb
|
44
55
|
- spec/models/person.rb
|
45
|
-
- spec/models/pet.rb
|
46
56
|
- spec/rgviz/executor_spec.rb
|
47
|
-
- README
|
57
|
+
- README.rdoc
|
48
58
|
has_rdoc: true
|
49
59
|
homepage: http://code.google.com/p/rgviz-rails
|
50
60
|
licenses: []
|
@@ -59,7 +69,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
59
69
|
requirements:
|
60
70
|
- - ">="
|
61
71
|
- !ruby/object:Gem::Version
|
62
|
-
hash: 3
|
63
72
|
segments:
|
64
73
|
- 0
|
65
74
|
version: "0"
|
@@ -68,7 +77,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
77
|
requirements:
|
69
78
|
- - ">="
|
70
79
|
- !ruby/object:Gem::Version
|
71
|
-
hash: 3
|
72
80
|
segments:
|
73
81
|
- 0
|
74
82
|
version: "0"
|
data/README
DELETED
@@ -1,243 +0,0 @@
|
|
1
|
-
== Welcome to Rails
|
2
|
-
|
3
|
-
Rails is a web-application framework that includes everything needed to create
|
4
|
-
database-backed web applications according to the Model-View-Control pattern.
|
5
|
-
|
6
|
-
This pattern splits the view (also called the presentation) into "dumb" templates
|
7
|
-
that are primarily responsible for inserting pre-built data in between HTML tags.
|
8
|
-
The model contains the "smart" domain objects (such as Account, Product, Person,
|
9
|
-
Post) that holds all the business logic and knows how to persist themselves to
|
10
|
-
a database. The controller handles the incoming requests (such as Save New Account,
|
11
|
-
Update Product, Show Post) by manipulating the model and directing data to the view.
|
12
|
-
|
13
|
-
In Rails, the model is handled by what's called an object-relational mapping
|
14
|
-
layer entitled Active Record. This layer allows you to present the data from
|
15
|
-
database rows as objects and embellish these data objects with business logic
|
16
|
-
methods. You can read more about Active Record in
|
17
|
-
link:files/vendor/rails/activerecord/README.html.
|
18
|
-
|
19
|
-
The controller and view are handled by the Action Pack, which handles both
|
20
|
-
layers by its two parts: Action View and Action Controller. These two layers
|
21
|
-
are bundled in a single package due to their heavy interdependence. This is
|
22
|
-
unlike the relationship between the Active Record and Action Pack that is much
|
23
|
-
more separate. Each of these packages can be used independently outside of
|
24
|
-
Rails. You can read more about Action Pack in
|
25
|
-
link:files/vendor/rails/actionpack/README.html.
|
26
|
-
|
27
|
-
|
28
|
-
== Getting Started
|
29
|
-
|
30
|
-
1. At the command prompt, start a new Rails application using the <tt>rails</tt> command
|
31
|
-
and your application name. Ex: rails myapp
|
32
|
-
2. Change directory into myapp and start the web server: <tt>script/server</tt> (run with --help for options)
|
33
|
-
3. Go to http://localhost:3000/ and get "Welcome aboard: You're riding the Rails!"
|
34
|
-
4. Follow the guidelines to start developing your application
|
35
|
-
|
36
|
-
|
37
|
-
== Web Servers
|
38
|
-
|
39
|
-
By default, Rails will try to use Mongrel if it's are installed when started with script/server, otherwise Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails
|
40
|
-
with a variety of other web servers.
|
41
|
-
|
42
|
-
Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is
|
43
|
-
suitable for development and deployment of Rails applications. If you have Ruby Gems installed,
|
44
|
-
getting up and running with mongrel is as easy as: <tt>gem install mongrel</tt>.
|
45
|
-
More info at: http://mongrel.rubyforge.org
|
46
|
-
|
47
|
-
Say other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or
|
48
|
-
Lighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use
|
49
|
-
FCGI or proxy to a pack of Mongrels/Thin/Ebb servers.
|
50
|
-
|
51
|
-
== Apache .htaccess example for FCGI/CGI
|
52
|
-
|
53
|
-
# General Apache options
|
54
|
-
AddHandler fastcgi-script .fcgi
|
55
|
-
AddHandler cgi-script .cgi
|
56
|
-
Options +FollowSymLinks +ExecCGI
|
57
|
-
|
58
|
-
# If you don't want Rails to look in certain directories,
|
59
|
-
# use the following rewrite rules so that Apache won't rewrite certain requests
|
60
|
-
#
|
61
|
-
# Example:
|
62
|
-
# RewriteCond %{REQUEST_URI} ^/notrails.*
|
63
|
-
# RewriteRule .* - [L]
|
64
|
-
|
65
|
-
# Redirect all requests not available on the filesystem to Rails
|
66
|
-
# By default the cgi dispatcher is used which is very slow
|
67
|
-
#
|
68
|
-
# For better performance replace the dispatcher with the fastcgi one
|
69
|
-
#
|
70
|
-
# Example:
|
71
|
-
# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
|
72
|
-
RewriteEngine On
|
73
|
-
|
74
|
-
# If your Rails application is accessed via an Alias directive,
|
75
|
-
# then you MUST also set the RewriteBase in this htaccess file.
|
76
|
-
#
|
77
|
-
# Example:
|
78
|
-
# Alias /myrailsapp /path/to/myrailsapp/public
|
79
|
-
# RewriteBase /myrailsapp
|
80
|
-
|
81
|
-
RewriteRule ^$ index.html [QSA]
|
82
|
-
RewriteRule ^([^.]+)$ $1.html [QSA]
|
83
|
-
RewriteCond %{REQUEST_FILENAME} !-f
|
84
|
-
RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
|
85
|
-
|
86
|
-
# In case Rails experiences terminal errors
|
87
|
-
# Instead of displaying this message you can supply a file here which will be rendered instead
|
88
|
-
#
|
89
|
-
# Example:
|
90
|
-
# ErrorDocument 500 /500.html
|
91
|
-
|
92
|
-
ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
|
93
|
-
|
94
|
-
|
95
|
-
== Debugging Rails
|
96
|
-
|
97
|
-
Sometimes your application goes wrong. Fortunately there are a lot of tools that
|
98
|
-
will help you debug it and get it back on the rails.
|
99
|
-
|
100
|
-
First area to check is the application log files. Have "tail -f" commands running
|
101
|
-
on the server.log and development.log. Rails will automatically display debugging
|
102
|
-
and runtime information to these files. Debugging info will also be shown in the
|
103
|
-
browser on requests from 127.0.0.1.
|
104
|
-
|
105
|
-
You can also log your own messages directly into the log file from your code using
|
106
|
-
the Ruby logger class from inside your controllers. Example:
|
107
|
-
|
108
|
-
class WeblogController < ActionController::Base
|
109
|
-
def destroy
|
110
|
-
@weblog = Weblog.find(params[:id])
|
111
|
-
@weblog.destroy
|
112
|
-
logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
The result will be a message in your log file along the lines of:
|
117
|
-
|
118
|
-
Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1
|
119
|
-
|
120
|
-
More information on how to use the logger is at http://www.ruby-doc.org/core/
|
121
|
-
|
122
|
-
Also, Ruby documentation can be found at http://www.ruby-lang.org/ including:
|
123
|
-
|
124
|
-
* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/
|
125
|
-
* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
|
126
|
-
|
127
|
-
These two online (and free) books will bring you up to speed on the Ruby language
|
128
|
-
and also on programming in general.
|
129
|
-
|
130
|
-
|
131
|
-
== Debugger
|
132
|
-
|
133
|
-
Debugger support is available through the debugger command when you start your Mongrel or
|
134
|
-
Webrick server with --debugger. This means that you can break out of execution at any point
|
135
|
-
in the code, investigate and change the model, AND then resume execution!
|
136
|
-
You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug'
|
137
|
-
Example:
|
138
|
-
|
139
|
-
class WeblogController < ActionController::Base
|
140
|
-
def index
|
141
|
-
@posts = Post.find(:all)
|
142
|
-
debugger
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
So the controller will accept the action, run the first line, then present you
|
147
|
-
with a IRB prompt in the server window. Here you can do things like:
|
148
|
-
|
149
|
-
>> @posts.inspect
|
150
|
-
=> "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
|
151
|
-
#<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
|
152
|
-
>> @posts.first.title = "hello from a debugger"
|
153
|
-
=> "hello from a debugger"
|
154
|
-
|
155
|
-
...and even better is that you can examine how your runtime objects actually work:
|
156
|
-
|
157
|
-
>> f = @posts.first
|
158
|
-
=> #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
|
159
|
-
>> f.
|
160
|
-
Display all 152 possibilities? (y or n)
|
161
|
-
|
162
|
-
Finally, when you're ready to resume execution, you enter "cont"
|
163
|
-
|
164
|
-
|
165
|
-
== Console
|
166
|
-
|
167
|
-
You can interact with the domain model by starting the console through <tt>script/console</tt>.
|
168
|
-
Here you'll have all parts of the application configured, just like it is when the
|
169
|
-
application is running. You can inspect domain models, change values, and save to the
|
170
|
-
database. Starting the script without arguments will launch it in the development environment.
|
171
|
-
Passing an argument will specify a different environment, like <tt>script/console production</tt>.
|
172
|
-
|
173
|
-
To reload your controllers and models after launching the console run <tt>reload!</tt>
|
174
|
-
|
175
|
-
== dbconsole
|
176
|
-
|
177
|
-
You can go to the command line of your database directly through <tt>script/dbconsole</tt>.
|
178
|
-
You would be connected to the database with the credentials defined in database.yml.
|
179
|
-
Starting the script without arguments will connect you to the development database. Passing an
|
180
|
-
argument will connect you to a different database, like <tt>script/dbconsole production</tt>.
|
181
|
-
Currently works for mysql, postgresql and sqlite.
|
182
|
-
|
183
|
-
== Description of Contents
|
184
|
-
|
185
|
-
app
|
186
|
-
Holds all the code that's specific to this particular application.
|
187
|
-
|
188
|
-
app/controllers
|
189
|
-
Holds controllers that should be named like weblogs_controller.rb for
|
190
|
-
automated URL mapping. All controllers should descend from ApplicationController
|
191
|
-
which itself descends from ActionController::Base.
|
192
|
-
|
193
|
-
app/models
|
194
|
-
Holds models that should be named like post.rb.
|
195
|
-
Most models will descend from ActiveRecord::Base.
|
196
|
-
|
197
|
-
app/views
|
198
|
-
Holds the template files for the view that should be named like
|
199
|
-
weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby
|
200
|
-
syntax.
|
201
|
-
|
202
|
-
app/views/layouts
|
203
|
-
Holds the template files for layouts to be used with views. This models the common
|
204
|
-
header/footer method of wrapping views. In your views, define a layout using the
|
205
|
-
<tt>layout :default</tt> and create a file named default.html.erb. Inside default.html.erb,
|
206
|
-
call <% yield %> to render the view using this layout.
|
207
|
-
|
208
|
-
app/helpers
|
209
|
-
Holds view helpers that should be named like weblogs_helper.rb. These are generated
|
210
|
-
for you automatically when using script/generate for controllers. Helpers can be used to
|
211
|
-
wrap functionality for your views into methods.
|
212
|
-
|
213
|
-
config
|
214
|
-
Configuration files for the Rails environment, the routing map, the database, and other dependencies.
|
215
|
-
|
216
|
-
db
|
217
|
-
Contains the database schema in schema.rb. db/migrate contains all
|
218
|
-
the sequence of Migrations for your schema.
|
219
|
-
|
220
|
-
doc
|
221
|
-
This directory is where your application documentation will be stored when generated
|
222
|
-
using <tt>rake doc:app</tt>
|
223
|
-
|
224
|
-
lib
|
225
|
-
Application specific libraries. Basically, any kind of custom code that doesn't
|
226
|
-
belong under controllers, models, or helpers. This directory is in the load path.
|
227
|
-
|
228
|
-
public
|
229
|
-
The directory available for the web server. Contains subdirectories for images, stylesheets,
|
230
|
-
and javascripts. Also contains the dispatchers and the default HTML files. This should be
|
231
|
-
set as the DOCUMENT_ROOT of your web server.
|
232
|
-
|
233
|
-
script
|
234
|
-
Helper scripts for automation and generation.
|
235
|
-
|
236
|
-
test
|
237
|
-
Unit and functional tests along with fixtures. When using the script/generate scripts, template
|
238
|
-
test files will be generated for you and placed in this directory.
|
239
|
-
|
240
|
-
vendor
|
241
|
-
External libraries that the application depends on. Also includes the plugins subdirectory.
|
242
|
-
If the app has frozen rails, those gems also go here, under vendor/rails/.
|
243
|
-
This directory is in the load path.
|
data/spec/models/pet.rb
DELETED