ruport 0.8.14 → 0.10.0
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.
- data/README +42 -107
- data/Rakefile +29 -32
- data/examples/centered_pdf_text_box.rb +13 -19
- data/examples/example.csv +3 -0
- data/examples/line_plotter.rb +15 -15
- data/examples/pdf_complex_report.rb +10 -23
- data/examples/pdf_table_with_title.rb +12 -12
- data/examples/rope_examples/itunes/Rakefile +22 -1
- data/examples/rope_examples/itunes/config/environment.rb +4 -0
- data/examples/rope_examples/itunes/lib/init.rb +32 -2
- data/examples/rope_examples/itunes/util/build +50 -16
- data/examples/rope_examples/sales_report/README +1 -1
- data/examples/rope_examples/sales_report/Rakefile +22 -1
- data/examples/rope_examples/sales_report/config/environment.rb +4 -0
- data/examples/rope_examples/sales_report/lib/init.rb +32 -2
- data/examples/rope_examples/sales_report/lib/reports/sales.rb +10 -16
- data/examples/rope_examples/sales_report/util/build +50 -16
- data/examples/row_renderer.rb +39 -0
- data/examples/ruport_list/png_embed.rb +61 -0
- data/examples/ruport_list/roadmap.png +0 -0
- data/examples/sample.rb +16 -0
- data/examples/simple_pdf_lines.rb +24 -0
- data/lib/ruport.rb +143 -57
- data/lib/ruport/acts_as_reportable.rb +246 -0
- data/lib/ruport/data.rb +1 -2
- data/lib/ruport/data/grouping.rb +311 -0
- data/lib/ruport/data/record.rb +113 -84
- data/lib/ruport/data/table.rb +275 -174
- data/lib/ruport/formatter.rb +149 -0
- data/lib/ruport/formatter/csv.rb +87 -0
- data/lib/ruport/formatter/html.rb +89 -0
- data/lib/ruport/formatter/pdf.rb +357 -0
- data/lib/ruport/formatter/text.rb +151 -0
- data/lib/ruport/generator.rb +127 -30
- data/lib/ruport/query.rb +46 -99
- data/lib/ruport/renderer.rb +238 -194
- data/lib/ruport/renderer/grouping.rb +67 -0
- data/lib/ruport/renderer/table.rb +25 -98
- data/lib/ruport/report.rb +45 -96
- data/test/acts_as_reportable_test.rb +229 -0
- data/test/csv_formatter_test.rb +97 -0
- data/test/{_test_database.rb → database_test_.rb} +0 -0
- data/test/grouping_test.rb +305 -0
- data/test/html_formatter_test.rb +104 -0
- data/test/pdf_formatter_test.rb +25 -0
- data/test/{test_query.rb → query_test.rb} +32 -121
- data/test/{test_record.rb → record_test.rb} +40 -23
- data/test/renderer_test.rb +344 -0
- data/test/{test_report.rb → report_test.rb} +74 -44
- data/test/samples/ticket_count.csv +124 -0
- data/test/{test_sql_split.rb → sql_split_test.rb} +0 -0
- data/test/{test_table.rb → table_test.rb} +255 -44
- data/test/text_formatter_test.rb +144 -0
- data/util/bench/data/record/bench_as_vs_to.rb +17 -0
- data/util/bench/data/record/bench_constructor.rb +46 -0
- data/util/bench/data/record/bench_indexing.rb +65 -0
- data/util/bench/data/record/bench_reorder.rb +35 -0
- data/util/bench/data/record/bench_to_a.rb +19 -0
- data/util/bench/data/table/bench_column_manip.rb +103 -0
- data/util/bench/data/table/bench_dup.rb +24 -0
- data/util/bench/data/table/bench_init.rb +67 -0
- data/util/bench/data/table/bench_manip.rb +125 -0
- data/util/bench/formatter/bench_csv.rb +14 -0
- data/util/bench/formatter/bench_html.rb +14 -0
- data/util/bench/formatter/bench_pdf.rb +14 -0
- data/util/bench/formatter/bench_text.rb +14 -0
- data/util/bench/samples/tattle.csv +1237 -0
- metadata +121 -143
- data/TODO +0 -21
- data/examples/invoice.rb +0 -142
- data/examples/invoice_report.rb +0 -29
- data/examples/line_graph.rb +0 -38
- data/examples/rope_examples/itunes/config/ruport_config.rb +0 -8
- data/examples/rope_examples/sales_report/config/ruport_config.rb +0 -8
- data/lib/ruport/attempt.rb +0 -63
- data/lib/ruport/config.rb +0 -204
- data/lib/ruport/data/groupable.rb +0 -93
- data/lib/ruport/data/taggable.rb +0 -80
- data/lib/ruport/format.rb +0 -1
- data/lib/ruport/format/csv.rb +0 -29
- data/lib/ruport/format/html.rb +0 -42
- data/lib/ruport/format/latex.rb +0 -47
- data/lib/ruport/format/pdf.rb +0 -233
- data/lib/ruport/format/plugin.rb +0 -31
- data/lib/ruport/format/svg.rb +0 -60
- data/lib/ruport/format/text.rb +0 -103
- data/lib/ruport/format/xml.rb +0 -32
- data/lib/ruport/layout.rb +0 -1
- data/lib/ruport/layout/component.rb +0 -7
- data/lib/ruport/mailer.rb +0 -99
- data/lib/ruport/renderer/graph.rb +0 -46
- data/lib/ruport/report/graph.rb +0 -14
- data/lib/ruport/system_extensions.rb +0 -71
- data/test/test_config.rb +0 -88
- data/test/test_format_text.rb +0 -63
- data/test/test_graph_renderer.rb +0 -97
- data/test/test_groupable.rb +0 -56
- data/test/test_mailer.rb +0 -170
- data/test/test_renderer.rb +0 -151
- data/test/test_ruport.rb +0 -58
- data/test/test_table_renderer.rb +0 -141
- data/test/test_taggable.rb +0 -52
data/examples/invoice_report.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
require "rubygems"
|
2
|
-
require "ruport"
|
3
|
-
require "invoice"
|
4
|
-
|
5
|
-
class SampleReport < Ruport::Report
|
6
|
-
include Invoice
|
7
|
-
|
8
|
-
def generate
|
9
|
-
render_invoice do |i|
|
10
|
-
i.data = [[1,2,3],[4,5,6]].to_table(%w[a b c])
|
11
|
-
|
12
|
-
i.options do |o|
|
13
|
-
o.company_info = "Stone Code Productions\n43 Neagle Street"
|
14
|
-
o.customer_info = "Gregory Brown\n200 Foo Ave."
|
15
|
-
o.comments = "J. Random Comment"
|
16
|
-
o.order_info = "Some info\nabout your order"
|
17
|
-
o.title = "Invoice for 12.15.2006 - 12.31.2006"
|
18
|
-
end
|
19
|
-
|
20
|
-
i.layout do |lay|
|
21
|
-
lay.body_width = 480
|
22
|
-
lay.comments_font_size = 12
|
23
|
-
lay.title_font_size = 10
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
SampleReport.run { |r| r.write "out.pdf" }
|
data/examples/line_graph.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
require "ruport"
|
2
|
-
table = Table(%w[jan feb mar apr may jun jul]) do |t|
|
3
|
-
t << [5,7,9,12,14,16,18]
|
4
|
-
t << [21,3,8,19,13,15,1]
|
5
|
-
end
|
6
|
-
|
7
|
-
table[0].tag :ghosts
|
8
|
-
table[1].tag "pirates"
|
9
|
-
|
10
|
-
results = Ruport::Renderer::Graph.render_svg do |r|
|
11
|
-
|
12
|
-
r.data = table
|
13
|
-
|
14
|
-
r.options.title = "Simple Line Graph"
|
15
|
-
|
16
|
-
r.layout do |l|
|
17
|
-
l.width = 700
|
18
|
-
l.height = 500
|
19
|
-
l.theme = r.plugin.themes[:keynote]
|
20
|
-
l.style = :line
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
File.open("line_graph.svg","w") { |f| f << results }
|
25
|
-
|
26
|
-
PAGE = <<-END_HTML
|
27
|
-
<html>
|
28
|
-
<body>
|
29
|
-
<embed
|
30
|
-
src="line_graph.svg"
|
31
|
-
width="700"
|
32
|
-
height="500"
|
33
|
-
/>
|
34
|
-
</body>
|
35
|
-
<html>
|
36
|
-
END_HTML
|
37
|
-
|
38
|
-
File.open("line_graph.html","w") { |f| f << PAGE }
|
data/lib/ruport/attempt.rb
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
require 'timeout'
|
2
|
-
|
3
|
-
class Attempt # :nodoc:
|
4
|
-
VERSION = '0.1.0'
|
5
|
-
|
6
|
-
# Number of attempts to make before failing. The default is 3.
|
7
|
-
attr_accessor :tries
|
8
|
-
|
9
|
-
# Number of seconds to wait between attempts. The default is 60.
|
10
|
-
attr_accessor :interval
|
11
|
-
|
12
|
-
# a level which ruport understands.
|
13
|
-
attr_accessor :log_level
|
14
|
-
|
15
|
-
# If set, this increments the interval with each failed attempt by that
|
16
|
-
# number of seconds.
|
17
|
-
attr_accessor :increment
|
18
|
-
|
19
|
-
# If set, the code block is further wrapped in a timeout block.
|
20
|
-
attr_accessor :timeout
|
21
|
-
|
22
|
-
# Determines which exception level to check when looking for errors to
|
23
|
-
# retry. The default is 'Exception' (i.e. all errors).
|
24
|
-
attr_accessor :level
|
25
|
-
|
26
|
-
# :call-seq:
|
27
|
-
# Attempt.new{ |a| ... }
|
28
|
-
#
|
29
|
-
# Creates and returns a new +Attempt+ object. Use a block to set the
|
30
|
-
# accessors.
|
31
|
-
#
|
32
|
-
def initialize
|
33
|
-
@tries = 3 # Reasonable default
|
34
|
-
@interval = 60 # Reasonable default
|
35
|
-
@increment = nil # Should be an int, if provided
|
36
|
-
@timeout = nil # Wrap the code in a timeout block if provided
|
37
|
-
@level = Exception # Level of exception to be caught
|
38
|
-
|
39
|
-
yield self if block_given?
|
40
|
-
end
|
41
|
-
|
42
|
-
def attempt
|
43
|
-
count = 1
|
44
|
-
begin
|
45
|
-
if @timeout
|
46
|
-
Timeout.timeout(@timeout){ yield }
|
47
|
-
else
|
48
|
-
yield
|
49
|
-
end
|
50
|
-
rescue @level => error
|
51
|
-
@tries -= 1
|
52
|
-
if @tries > 0
|
53
|
-
msg = "Error on attempt # #{count}: #{error}; retrying"
|
54
|
-
count += 1
|
55
|
-
Ruport.log(msg, :level => log_level)
|
56
|
-
@interval += @increment if @increment
|
57
|
-
sleep @interval
|
58
|
-
retry
|
59
|
-
end
|
60
|
-
raise
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
data/lib/ruport/config.rb
DELETED
@@ -1,204 +0,0 @@
|
|
1
|
-
# ruport/config.rb : Ruby Reports configuration system
|
2
|
-
#
|
3
|
-
# Author: Gregory T. Brown (gregory.t.brown at gmail dot com)
|
4
|
-
#
|
5
|
-
# Copyright (c) 2006, All Rights Reserved.
|
6
|
-
#
|
7
|
-
# This is free software. You may modify and redistribute this freely under
|
8
|
-
# your choice of the GNU General Public License or the Ruby License.
|
9
|
-
#
|
10
|
-
# See LICENSE and COPYING for details
|
11
|
-
#
|
12
|
-
require "ostruct"
|
13
|
-
module Ruport
|
14
|
-
|
15
|
-
# === Overview
|
16
|
-
#
|
17
|
-
# This class serves as the configuration system for Ruport.
|
18
|
-
#
|
19
|
-
# The source and mailer defined as <tt>:default</tt> will become the
|
20
|
-
# fallback values if you don't specify one in <tt>Report::Mailer</tt> or
|
21
|
-
# <tt>Query</tt>, but you may define as many sources as you like and switch
|
22
|
-
# between them later.
|
23
|
-
#
|
24
|
-
# === Example
|
25
|
-
#
|
26
|
-
# The most common way to access your application configuration is through
|
27
|
-
# the <tt>Ruport.configure</tt> method, like this:
|
28
|
-
#
|
29
|
-
# Ruport.configure do |config|
|
30
|
-
#
|
31
|
-
# config.log_file 'foo.log'
|
32
|
-
# config.debug_mode = true
|
33
|
-
#
|
34
|
-
# config.source :default,
|
35
|
-
# :dsn => "dbi:mysql:somedb:db.blixy.org",
|
36
|
-
# :user => "root",
|
37
|
-
# :password => "chunky_bacon"
|
38
|
-
#
|
39
|
-
# config.mailer :default,
|
40
|
-
# :host => "mail.chunkybacon.org",
|
41
|
-
# :address => "chunky@bacon.net",
|
42
|
-
# :user => "cartoon",
|
43
|
-
# :password => "fox",
|
44
|
-
# :port => 25,
|
45
|
-
# :auth_type => :login
|
46
|
-
#
|
47
|
-
# end
|
48
|
-
#
|
49
|
-
# You can accomplish the same thing by opening the class directly:
|
50
|
-
#
|
51
|
-
# class Ruport::Config
|
52
|
-
#
|
53
|
-
# source :default,
|
54
|
-
# :dsn => "dbi:mysql:some_db",
|
55
|
-
# :user => "root"
|
56
|
-
#
|
57
|
-
# mailer :default,
|
58
|
-
# :host => "mail.iheartwhy.com",
|
59
|
-
# :address => "sandal@ruby-harmonix.net",
|
60
|
-
# :user => "sandal",
|
61
|
-
# :password => "abc123"
|
62
|
-
#
|
63
|
-
# logfile 'foo.log'
|
64
|
-
#
|
65
|
-
# end
|
66
|
-
#
|
67
|
-
# Saving this config information into a file and then requiring it allows
|
68
|
-
# you to share configurations between Ruport applications.
|
69
|
-
#
|
70
|
-
module Config
|
71
|
-
module_function
|
72
|
-
|
73
|
-
# :call-seq:
|
74
|
-
# source(source_name, options)
|
75
|
-
#
|
76
|
-
# Creates or retrieves a database source configuration. Available options
|
77
|
-
# are:
|
78
|
-
# <b><tt>:user</tt></b>:: The user used to connect to the database.
|
79
|
-
# <b><tt>:password</tt></b>:: The password to use to connect to the
|
80
|
-
# database (optional).
|
81
|
-
# <b><tt>:dsn</tt></b>:: The dsn string that dbi will use to
|
82
|
-
# access the database.
|
83
|
-
#
|
84
|
-
# Example (setting a source):
|
85
|
-
# source :default, :user => "root",
|
86
|
-
# :password => "clyde",
|
87
|
-
# :dsn => "dbi:mysql:blinkybase"
|
88
|
-
#
|
89
|
-
# Example (retrieving a source):
|
90
|
-
# db = source(:default) #=> <OpenStruct ..>
|
91
|
-
# db.dsn #=> "dbi:mysql:blinkybase"
|
92
|
-
#
|
93
|
-
def source(*args)
|
94
|
-
return sources[args.first] if args.length == 1
|
95
|
-
sources[args.first] = OpenStruct.new(*args[1..-1])
|
96
|
-
check_source(sources[args.first],args.first)
|
97
|
-
end
|
98
|
-
|
99
|
-
# :call-seq:
|
100
|
-
# mailer(mailer_name, options)
|
101
|
-
#
|
102
|
-
# Creates or retrieves a mailer configuration. Available options:
|
103
|
-
# <b><tt>:host</tt></b>:: The SMTP host to use.
|
104
|
-
# <b><tt>:address</tt></b>:: Address the email is being sent from.
|
105
|
-
# <b><tt>:user</tt></b>:: The username to use on the SMTP server
|
106
|
-
# <b><tt>:password</tt></b>:: The password to use on the SMTP server.
|
107
|
-
# Optional.
|
108
|
-
# <b><tt>:port</tt></b>:: The SMTP port to use. Optional, defaults
|
109
|
-
# to 25.
|
110
|
-
# <b><tt>:auth_type</tt></b>:: SMTP authorization method. Optional,
|
111
|
-
# defaults to <tt>:plain</tt>.
|
112
|
-
# <b><tt>:mail_klass</tt></b>:: If you don't want to use the default
|
113
|
-
# <tt>MailFactory</tt> object, you can
|
114
|
-
# pass another mailer to use here.
|
115
|
-
#
|
116
|
-
# Example (creating a mailer config):
|
117
|
-
# mailer :alternate, :host => "mail.test.com",
|
118
|
-
# :address => "test@test.com",
|
119
|
-
# :user => "test",
|
120
|
-
# :password => "blinky"
|
121
|
-
# :auth_type => :cram_md5
|
122
|
-
#
|
123
|
-
# Example (retreiving a mailer config):
|
124
|
-
# mail_conf = mailer(:alternate) #=> <OpenStruct ..>
|
125
|
-
# mail_conf.address #=> test@test.com
|
126
|
-
#
|
127
|
-
def mailer(*args)
|
128
|
-
return mailers[args.first] if args.length == 1
|
129
|
-
mailers[args.first] = OpenStruct.new(*args[1..-1])
|
130
|
-
check_mailer(mailers[args.first],args.first)
|
131
|
-
end
|
132
|
-
|
133
|
-
# The file that <tt>Ruport.log()</tt> will write to.
|
134
|
-
def log_file(file)
|
135
|
-
@logger = Logger.new(file)
|
136
|
-
end
|
137
|
-
|
138
|
-
# Same as <tt>Config.log_file</tt>, but accessor style.
|
139
|
-
def log_file=(file)
|
140
|
-
log_file(file)
|
141
|
-
end
|
142
|
-
|
143
|
-
# Alias for <tt>sources[:default]</tt>.
|
144
|
-
def default_source
|
145
|
-
sources[:default]
|
146
|
-
end
|
147
|
-
|
148
|
-
# Alias for <tt>mailers[:default]</tt>.
|
149
|
-
def default_mailer
|
150
|
-
mailers[:default]
|
151
|
-
end
|
152
|
-
|
153
|
-
# Returns all <tt>source</tt>s defined in this <tt>Config</tt>.
|
154
|
-
def sources; @sources ||= {}; end
|
155
|
-
|
156
|
-
# Returns all the <tt>mailer</tt>s defined in this <tt>Config</tt>.
|
157
|
-
def mailers; @mailers ||= {}; end
|
158
|
-
|
159
|
-
# Returns the currently active logger.
|
160
|
-
def logger; @logger; end
|
161
|
-
|
162
|
-
# returns true if in debug mode
|
163
|
-
def debug_mode?; !!@debug_mode; end
|
164
|
-
|
165
|
-
# Verifies that you have provided a DSN for your source.
|
166
|
-
def check_source(settings,label) # :nodoc:
|
167
|
-
unless settings.dsn
|
168
|
-
Ruport.log(
|
169
|
-
"Missing DSN for source #{label}!",
|
170
|
-
:status => :fatal, :level => :log_only,
|
171
|
-
:raises => ArgumentError
|
172
|
-
)
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
# Verifies that you have provided a host for your mailer.
|
177
|
-
def check_mailer(settings, label) # :nodoc:
|
178
|
-
unless settings.host
|
179
|
-
Ruport.log(
|
180
|
-
"Missing host for mailer #{label}",
|
181
|
-
:status => :fatal, :level => :log_only,
|
182
|
-
:raises => ArgumentError
|
183
|
-
)
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
# forces messages with :level of :log_only to be printed
|
188
|
-
def debug_mode=(something)
|
189
|
-
@debug_mode = !!something
|
190
|
-
end
|
191
|
-
|
192
|
-
# Allows users to set their own accessors on the Config module
|
193
|
-
def method_missing(meth, *args)
|
194
|
-
@config ||= OpenStruct.new
|
195
|
-
|
196
|
-
if args.empty? || meth.to_s =~ /.*=/
|
197
|
-
@config.send(meth, *args)
|
198
|
-
else
|
199
|
-
@config.send("#{meth}=".to_sym, *args)
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
end
|
204
|
-
end
|
@@ -1,93 +0,0 @@
|
|
1
|
-
module Ruport::Data
|
2
|
-
|
3
|
-
#
|
4
|
-
# === Overview
|
5
|
-
#
|
6
|
-
# This module provides a simple mechanism for grouping objects based on
|
7
|
-
# tags.
|
8
|
-
#
|
9
|
-
module Groupable
|
10
|
-
|
11
|
-
#
|
12
|
-
# Creates a <tt>Record</tt> made up of <tt>Table</tt>s containing all the
|
13
|
-
# records in the original table with the same tag.
|
14
|
-
#
|
15
|
-
# Example:
|
16
|
-
# table = [['inky', 1],
|
17
|
-
# ['blinky',2],
|
18
|
-
# ['pinky', 3],
|
19
|
-
# ['clyde', 4]].to_table(['name','score'])
|
20
|
-
#
|
21
|
-
# table[0].tag("grp_winners")
|
22
|
-
# table[1].tag("grp_losers")
|
23
|
-
# table[2].tag("grp_winners")
|
24
|
-
# table[3].tag("grp_losers")
|
25
|
-
#
|
26
|
-
# r = table.groups
|
27
|
-
# puts r["winners"]
|
28
|
-
# => +---------------+
|
29
|
-
# | name | score |
|
30
|
-
# +---------------+
|
31
|
-
# | inky | 1 |
|
32
|
-
# | pinky | 3 |
|
33
|
-
# +---------------+
|
34
|
-
#
|
35
|
-
# puts r["losers"]
|
36
|
-
# => +----------------+
|
37
|
-
# | name | score |
|
38
|
-
# +----------------+
|
39
|
-
# | blinky | 2 |
|
40
|
-
# | clyde | 4 |
|
41
|
-
# +----------------+
|
42
|
-
#
|
43
|
-
def groups
|
44
|
-
r_tags = group_names_intern
|
45
|
-
tables_hash = Hash.new { |h,k| h[k] = Table(column_names) }
|
46
|
-
r_tags.each { |t|
|
47
|
-
tables_hash[t.gsub(/^grp_/,"")] = sub_table { |r| r.tags.include? t }}
|
48
|
-
r = Record.new tables_hash, :attributes => group_names
|
49
|
-
end
|
50
|
-
|
51
|
-
# Gets the names of the groups
|
52
|
-
def group_names
|
53
|
-
group_names_intern.map { |r| r.gsub(/^grp_/,"") }
|
54
|
-
end
|
55
|
-
|
56
|
-
# Gets a subtable of the rows matching the group name
|
57
|
-
#
|
58
|
-
def group(tag)
|
59
|
-
sub_table { |r| r.tags.include?("grp_#{tag}") }
|
60
|
-
end
|
61
|
-
|
62
|
-
#
|
63
|
-
# Tags each row of the <tt>Table</tt> for which the <tt>block</tt> is not
|
64
|
-
# false with <tt>label</tt>.
|
65
|
-
#
|
66
|
-
# Example:
|
67
|
-
# table = [['inky', 1],
|
68
|
-
# ['blinky',2],
|
69
|
-
# ['pinky', 3]].to_table(['name','score'])
|
70
|
-
#
|
71
|
-
# table.create_group(:cool_kids) {|r| r.score > 1}
|
72
|
-
# groups = table.groups
|
73
|
-
#
|
74
|
-
# puts groups["cool_kids"]
|
75
|
-
# => +----------------+
|
76
|
-
# | name | score |
|
77
|
-
# +----------------+
|
78
|
-
# | blinky | 2 |
|
79
|
-
# | pinky | 3 |
|
80
|
-
# +----------------+
|
81
|
-
#
|
82
|
-
def create_group(label,&block)
|
83
|
-
each { |r| block[r] && r.tag("grp_#{label}") }
|
84
|
-
end
|
85
|
-
|
86
|
-
private
|
87
|
-
|
88
|
-
def group_names_intern
|
89
|
-
map { |r| r.tags.select { |r| r =~ /^grp_/ } }.flatten.uniq
|
90
|
-
end
|
91
|
-
|
92
|
-
end
|
93
|
-
end
|