interview 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/interview/install/install_generator.rb +3 -0
- data/lib/generators/interview/install/templates/application.css +14 -0
- data/lib/generators/interview/install/templates/application.js +2 -0
- data/lib/generators/interview/install/templates/blueimp-gallery.css +226 -0
- data/lib/generators/interview/install/templates/blueimp-gallery.js +1341 -0
- data/lib/generators/interview/install/templates/bootstrap_interview.css.scss +28 -0
- data/lib/generators/interview/install/templates/interview.js.coffee +60 -2
- data/lib/generators/interview/install/templates/missing_thumb.png +0 -0
- data/lib/interview/association_attribute.rb +6 -8
- data/lib/interview/association_list_attribute.rb +3 -2
- data/lib/interview/attribute.rb +52 -30
- data/lib/interview/collapse_container.rb +20 -0
- data/lib/interview/condition_container.rb +19 -0
- data/lib/interview/dropdown.rb +16 -4
- data/lib/interview/form.rb +42 -47
- data/lib/interview/form_errors.rb +2 -2
- data/lib/interview/grid.rb +1 -1
- data/lib/interview/image_attribute.rb +55 -15
- data/lib/interview/image_gallery_attribute.rb +74 -0
- data/lib/interview/image_light_box.rb +19 -0
- data/lib/interview/link.rb +2 -1
- data/lib/interview/list.rb +8 -2
- data/lib/interview/media_object.rb +45 -0
- data/lib/interview/navigation_item.rb +19 -4
- data/lib/interview/navigation_item_old.rb +26 -0
- data/lib/interview/nested_form_add_link.rb +1 -1
- data/lib/interview/panel.rb +18 -0
- data/lib/interview/polymorphic_add_link.rb +33 -4
- data/lib/interview/search_form.rb +21 -0
- data/lib/interview/text.rb +33 -3
- data/lib/interview/version.rb +1 -1
- data/lib/interview/view.rb +28 -6
- data/lib/interview.rb +12 -0
- metadata +13 -2
@@ -0,0 +1,19 @@
|
|
1
|
+
module Interview
|
2
|
+
class ImageLightBox < Control
|
3
|
+
|
4
|
+
def render
|
5
|
+
html = Builder::XmlMarkup.new
|
6
|
+
html.div id: "blueimp-gallery", class: "blueimp-gallery" do
|
7
|
+
html.div '', class: "slides"
|
8
|
+
html.h3 '', class: "title"
|
9
|
+
html.a '‹', class: "prev"
|
10
|
+
html.a '›', class: "next"
|
11
|
+
html.a '×', class: "close"
|
12
|
+
html.a '', class: "play-pause"
|
13
|
+
html.ol '', class: "indicator"
|
14
|
+
end
|
15
|
+
return html.target!
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/lib/interview/link.rb
CHANGED
@@ -37,13 +37,14 @@ module Interview
|
|
37
37
|
|
38
38
|
if @url
|
39
39
|
url = @url
|
40
|
+
url = "#{h.root_url}#{url}" if url[0..3] != 'http'
|
40
41
|
elsif @controller
|
41
42
|
url_options = @url_option.dup
|
42
43
|
opts = {controller: @controller, action: @action}.merge @url_params
|
43
44
|
url = h.url_for opts
|
44
45
|
else
|
45
46
|
url_params = @url_params.dup
|
46
|
-
url_params[:action] = @action if @action and not %w(index show destroy).include? @action
|
47
|
+
url_params[:action] = @action if @action and not %w(index show create update destroy).include? @action
|
47
48
|
object = @object || find_attribute!(:object)
|
48
49
|
url_params.each do |key, value|
|
49
50
|
url_params[key] = value.call(self) if value.is_a? Proc
|
data/lib/interview/list.rb
CHANGED
@@ -2,18 +2,24 @@ module Interview
|
|
2
2
|
class List < Control
|
3
3
|
include HasControls
|
4
4
|
|
5
|
-
attr_accessor :objects
|
5
|
+
attr_accessor :objects, :empty_message
|
6
6
|
attr_reader :object
|
7
7
|
|
8
8
|
def render
|
9
9
|
objects = @objects || find_attribute!(:objects)
|
10
|
+
puts "objects: #{objects.size}"
|
10
11
|
html = Builder::XmlMarkup.new
|
11
12
|
objects.each do |object|
|
12
13
|
@object = object
|
13
|
-
@controls.each do |control|
|
14
|
+
@controls.each do |control| # todo: controls neu initialisieren?
|
14
15
|
html << control.render
|
15
16
|
end
|
16
17
|
end
|
18
|
+
if objects.empty? and @empty_message != :hide
|
19
|
+
object = find_attribute!(:object)
|
20
|
+
empty_message = @empty_message || "Keine #{object.class.human_name(count: 2)} vorhanden."
|
21
|
+
html.p empty_message, class: 'text-center text-muted'
|
22
|
+
end
|
17
23
|
return html.target!
|
18
24
|
end
|
19
25
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Interview
|
2
|
+
class MediaObject < Control
|
3
|
+
include HasControls
|
4
|
+
|
5
|
+
attr_reader :object
|
6
|
+
|
7
|
+
def render
|
8
|
+
image_size = @image_size || :thumb
|
9
|
+
object = find_attribute! :object
|
10
|
+
@object = object
|
11
|
+
html = Builder::XmlMarkup.new(indent: 2)
|
12
|
+
html.div class: 'media' do
|
13
|
+
@controls.first.html_class << 'pull-left'
|
14
|
+
html << @controls.first.render
|
15
|
+
html.div class: 'media-body' do
|
16
|
+
@controls[1].html_class << 'media-heading' if @controls[1].respond_to? 'html_class'
|
17
|
+
@controls[1..-1].each do |control|
|
18
|
+
html << control.render
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# html.a class: 'pull-left', href: '#' do
|
24
|
+
# html << h.image_tag(object.send(@image_method.to_sym).url(image_size), class: 'media-object')
|
25
|
+
# end
|
26
|
+
# html.div class: 'media-body' do
|
27
|
+
# html.h4 object.send(@heading_method.to_sym), class: 'media-heading' if @heading_method
|
28
|
+
# @controls.each do |control|
|
29
|
+
# html << control.render
|
30
|
+
# end
|
31
|
+
# if @children_method
|
32
|
+
# object.send(@children_method).each do |child_object|
|
33
|
+
# @object = child_object
|
34
|
+
# media_object = MediaObject.new(parent: self, image_method: @image_method, heading_method: @heading_method, children_method: @children_method)
|
35
|
+
# media_object.add_controls(self.controls)
|
36
|
+
# html << media_object.render
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
return html.target!
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -1,18 +1,33 @@
|
|
1
1
|
module Interview
|
2
|
-
class NavigationItem <
|
2
|
+
class NavigationItem < Control
|
3
3
|
|
4
4
|
include HasControls
|
5
5
|
|
6
|
-
attr_accessor :active
|
6
|
+
attr_accessor :active, :image, :caption, :badge_formula,
|
7
|
+
:url, :controller, :object, :action, :http_method, :trail
|
7
8
|
|
8
9
|
def render
|
9
10
|
css_class = "level#{ancestors.count-1}"
|
10
11
|
css_class += " active" if @active
|
11
12
|
show_siblings = @active || siblings.any? { |sib| sib.active }
|
12
13
|
|
14
|
+
link = Link.new(parent: self, url: @url, controller: @controller, object: @object,
|
15
|
+
action: @action, http_method: @http_method, trail: @trail)
|
16
|
+
badge = @badge_formula.call if @badge_formula
|
17
|
+
unless badge.nil? or badge == 0 or badge == ''
|
18
|
+
link.add_control(HtmlControl.new do |html|
|
19
|
+
html.span badge, class: 'badge pull-right'
|
20
|
+
end)
|
21
|
+
end
|
22
|
+
link.add_control(HtmlControl.new do |html|
|
23
|
+
html.span '', class: "glyphicon glyphicon-#{@image}" if @image
|
24
|
+
html.text! ' ' if @image and @caption
|
25
|
+
html << @caption if @caption
|
26
|
+
end)
|
27
|
+
|
13
28
|
html = Builder::XmlMarkup.new
|
14
|
-
html.li class: css_class do
|
15
|
-
html <<
|
29
|
+
html.li class: css_class do
|
30
|
+
html << link.render
|
16
31
|
end
|
17
32
|
if show_siblings
|
18
33
|
@controls.each do |c|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Interview
|
2
|
+
class NavigationItem < Link
|
3
|
+
|
4
|
+
include HasControls
|
5
|
+
|
6
|
+
attr_accessor :active
|
7
|
+
|
8
|
+
def render
|
9
|
+
css_class = "level#{ancestors.count-1}"
|
10
|
+
css_class += " active" if @active
|
11
|
+
show_siblings = @active || siblings.any? { |sib| sib.active }
|
12
|
+
|
13
|
+
html = Builder::XmlMarkup.new
|
14
|
+
html.li class: css_class do
|
15
|
+
html << super
|
16
|
+
end
|
17
|
+
if show_siblings
|
18
|
+
@controls.each do |c|
|
19
|
+
html << c.render
|
20
|
+
end
|
21
|
+
end
|
22
|
+
return html.target!
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -38,7 +38,7 @@ module Interview
|
|
38
38
|
|
39
39
|
select_options = [[ h.t('helpers.select.prompt'), nil ]]
|
40
40
|
select_options += poly_classes.map do |poly_class|
|
41
|
-
[ poly_class.
|
41
|
+
[ poly_class.human_name, poly_class.name ]
|
42
42
|
end
|
43
43
|
|
44
44
|
data_content = {}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Interview
|
2
|
+
class Panel < Control
|
3
|
+
include HasControls
|
4
|
+
|
5
|
+
def render
|
6
|
+
html = Builder::XmlMarkup.new
|
7
|
+
html.div class: 'panel panel-default' do
|
8
|
+
html.div class: 'panel-body' do
|
9
|
+
@controls.each do |control|
|
10
|
+
html << control.render
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
return html.target!
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -1,16 +1,38 @@
|
|
1
1
|
module Interview
|
2
2
|
class PolymorphicAddLink < Control
|
3
3
|
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :object, :style, :nested_resource
|
5
5
|
|
6
6
|
def render
|
7
|
-
|
8
|
-
|
7
|
+
if @style and respond_to?("render_#{@style}_style", true)
|
8
|
+
return send("render_#{@style}_style")
|
9
|
+
else
|
10
|
+
return render_dropdown_style
|
9
11
|
end
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def render_dropdown_style
|
17
|
+
poly_classes = get_poly_classes
|
18
|
+
|
19
|
+
dropdown = Interview::Dropdown.new parent: self, image: 'plus'
|
20
|
+
poly_classes.each do |poly_class|
|
21
|
+
dropdown.add_control Interview::Link.new caption: poly_class.human_name,
|
22
|
+
object: poly_class.new,
|
23
|
+
action: 'new',
|
24
|
+
filter: { type: poly_class.name },
|
25
|
+
nested_resource: @nested_resource
|
26
|
+
end
|
27
|
+
return dropdown.render
|
28
|
+
end
|
29
|
+
|
30
|
+
def render_select_style
|
31
|
+
poly_classes = get_poly_classes
|
10
32
|
|
11
33
|
select_options = [[ h.t('helpers.select.prompt'), nil ]]
|
12
34
|
select_options += poly_classes.map do |poly_class|
|
13
|
-
[ poly_class.
|
35
|
+
[ poly_class.human_name, poly_class.name ]
|
14
36
|
end
|
15
37
|
|
16
38
|
html = Builder::XmlMarkup.new
|
@@ -24,5 +46,12 @@ module Interview
|
|
24
46
|
return html.target!
|
25
47
|
end
|
26
48
|
|
49
|
+
def get_poly_classes
|
50
|
+
object = @object || find_attribute!(:object)
|
51
|
+
return object.class::TYPE_OPTIONS.map do |option|
|
52
|
+
option.constantize
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
27
56
|
end
|
28
57
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Interview
|
2
|
+
class SearchForm < Control
|
3
|
+
|
4
|
+
def render
|
5
|
+
object = find_attribute! :object
|
6
|
+
return h.form_tag(h.polymorphic_path(object.class.model_name.plural, action: 'search'), method: :get, class: 'form-inline', role: 'form') do
|
7
|
+
html = Builder::XmlMarkup.new(indent: 2)
|
8
|
+
html.div do
|
9
|
+
html.div class: 'form-group' do
|
10
|
+
html << h.text_field_tag(:search, h.params[:search], class: 'form-control')
|
11
|
+
end
|
12
|
+
html.div class: 'form-group' do
|
13
|
+
html << h.submit_tag('Suchen', :name => nil, class: 'form-control btn btn-primary') # todo: 'Suchen' ändern
|
14
|
+
end
|
15
|
+
end
|
16
|
+
html.target!.html_safe
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/lib/interview/text.rb
CHANGED
@@ -1,13 +1,43 @@
|
|
1
1
|
module Interview
|
2
2
|
class Text < Control
|
3
|
+
include HasControls
|
3
4
|
|
4
|
-
attr_accessor :text, :bold
|
5
|
+
attr_accessor :text, :style, :bold, :html_class
|
6
|
+
|
7
|
+
def initialize(params={})
|
8
|
+
@html_class = []
|
9
|
+
super
|
10
|
+
end
|
5
11
|
|
6
12
|
def render
|
13
|
+
html = Builder::XmlMarkup.new
|
14
|
+
if @style
|
15
|
+
opts = {}
|
16
|
+
opts[:class] = @html_class.join(' ') unless @html_class.empty?
|
17
|
+
html.tag! @style.to_sym, opts do
|
18
|
+
render_text(html)
|
19
|
+
end
|
20
|
+
else
|
21
|
+
render_text(html)
|
22
|
+
end
|
23
|
+
return html.target!
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def render_text(html)
|
7
29
|
if @bold
|
8
|
-
|
30
|
+
html.b do
|
31
|
+
html.text! @text
|
32
|
+
@controls.each do |control|
|
33
|
+
html << control.render
|
34
|
+
end
|
35
|
+
end
|
9
36
|
else
|
10
|
-
|
37
|
+
html.text! @text if @text
|
38
|
+
@controls.each do |control|
|
39
|
+
html << control.render
|
40
|
+
end
|
11
41
|
end
|
12
42
|
end
|
13
43
|
|
data/lib/interview/version.rb
CHANGED
data/lib/interview/view.rb
CHANGED
@@ -18,13 +18,13 @@ module Interview
|
|
18
18
|
if @assoc_object
|
19
19
|
@assoc_object.class.human_attribute_name(@assoc_method)
|
20
20
|
else
|
21
|
-
@object.class.
|
21
|
+
@object.class.human_name(count: 2)
|
22
22
|
end
|
23
23
|
when :object
|
24
24
|
@object.human_id
|
25
25
|
when :form
|
26
26
|
text = @object.created_at ? 'views.edit' : 'views.new'
|
27
|
-
h.t(text, model: @object.class.
|
27
|
+
h.t(text, model: @object.class.human_name )
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
@@ -34,16 +34,28 @@ module Interview
|
|
34
34
|
if @assoc_object
|
35
35
|
return @assoc_object.class.human_attribute_name(@assoc_method.to_s.singularize)
|
36
36
|
else
|
37
|
-
return @object.class.
|
37
|
+
return @object.class.human_name
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
def tooltip
|
43
|
-
|
43
|
+
defaults = @object.class.lookup_ancestors.map do |klass|
|
44
|
+
klass.name.underscore.to_sym
|
45
|
+
end
|
46
|
+
defaults << ''
|
47
|
+
|
48
|
+
options = { :scope => [@object.class.i18n_scope, :model_tooltips], :default => defaults }
|
49
|
+
return I18n.translate(defaults.shift, options)
|
50
|
+
|
51
|
+
# return h.t("activerecord.model_tooltips.#{@object.class.name.underscore}", default: '')
|
52
|
+
# todo: tooltip von Elternelement verwenden, falls nicht vorhanden.
|
44
53
|
end
|
45
54
|
|
46
55
|
def render
|
56
|
+
tmp_object = @object
|
57
|
+
tmp_objects = @objects
|
58
|
+
|
47
59
|
if @object.is_a? String
|
48
60
|
# if Object::const_defined?(@object.camelcase) # todo: Überdenken!
|
49
61
|
# @object = @object.camelcase.constantize.new
|
@@ -51,15 +63,21 @@ module Interview
|
|
51
63
|
@assoc_object = find_attribute! :object
|
52
64
|
@object = @object.split('.').inject(@assoc_object, :send)
|
53
65
|
# end
|
66
|
+
elsif @object.is_a? Proc
|
67
|
+
@assoc_object = find_attribute! :object
|
68
|
+
@object = @object.call(@assoc_object, self)
|
54
69
|
elsif @object.is_a? Class
|
55
70
|
@object = @object.new
|
71
|
+
elsif @object.nil?
|
72
|
+
@object = find_attribute! :object
|
56
73
|
end
|
57
74
|
|
58
75
|
if @objects.is_a? String
|
59
76
|
@assoc_object = find_attribute! :object
|
60
77
|
@assoc_method = @objects
|
61
78
|
|
62
|
-
@objects = @assoc_object
|
79
|
+
@objects = @assoc_method.split('.').inject(@assoc_object, :send)
|
80
|
+
# @objects = @assoc_object.send @assoc_method
|
63
81
|
@objects = @objects.send @scope if @scope
|
64
82
|
@object = @objects.klass.new
|
65
83
|
@scope = nil
|
@@ -80,7 +98,11 @@ module Interview
|
|
80
98
|
end
|
81
99
|
end
|
82
100
|
|
83
|
-
|
101
|
+
html = @controls.map { |c| c.render }.join
|
102
|
+
|
103
|
+
@object = tmp_object
|
104
|
+
@objects = tmp_objects
|
105
|
+
return html
|
84
106
|
end
|
85
107
|
|
86
108
|
end
|
data/lib/interview.rb
CHANGED
@@ -24,6 +24,10 @@ require "interview/navigation_item"
|
|
24
24
|
require "interview/tab_box"
|
25
25
|
require "interview/tab"
|
26
26
|
require "interview/breadcrumbs"
|
27
|
+
require "interview/media_object"
|
28
|
+
require "interview/panel"
|
29
|
+
require "interview/collapse_container"
|
30
|
+
require "interview/condition_container"
|
27
31
|
|
28
32
|
require "interview/attribute"
|
29
33
|
require "interview/string_attribute"
|
@@ -36,6 +40,8 @@ require "interview/datetime_attribute"
|
|
36
40
|
require "interview/option_attribute"
|
37
41
|
require "interview/html_text_attribute"
|
38
42
|
require "interview/image_attribute"
|
43
|
+
require "interview/image_gallery_attribute"
|
44
|
+
require "interview/image_light_box"
|
39
45
|
require "interview/association_attribute"
|
40
46
|
require "interview/association_list_attribute"
|
41
47
|
require "interview/hidden_attribute"
|
@@ -48,6 +54,7 @@ require "interview/nested_form_remove_link"
|
|
48
54
|
require "interview/nested_form_add_link"
|
49
55
|
require "interview/form_errors"
|
50
56
|
require "interview/polymorphic_add_link"
|
57
|
+
require "interview/search_form"
|
51
58
|
|
52
59
|
require "interview/association_methods"
|
53
60
|
|
@@ -191,6 +198,11 @@ module ActiveRecord
|
|
191
198
|
return self.number_filter_to_sql(attr, filter)
|
192
199
|
end
|
193
200
|
|
201
|
+
def self.human_name(options={})
|
202
|
+
options = {scope: [self.i18n_scope, :models], count: 1}.merge(options)
|
203
|
+
I18n.translate(self.name.underscore, options)
|
204
|
+
end
|
205
|
+
|
194
206
|
end
|
195
207
|
end
|
196
208
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: interview
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jannes Köhler
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -97,7 +97,10 @@ files:
|
|
97
97
|
- lib/generators/interview/install/install_generator.rb
|
98
98
|
- lib/generators/interview/install/templates/32px.png
|
99
99
|
- lib/generators/interview/install/templates/40px.png
|
100
|
+
- lib/generators/interview/install/templates/application.css
|
100
101
|
- lib/generators/interview/install/templates/application.js
|
102
|
+
- lib/generators/interview/install/templates/blueimp-gallery.css
|
103
|
+
- lib/generators/interview/install/templates/blueimp-gallery.js
|
101
104
|
- lib/generators/interview/install/templates/bootstrap_interview.css.scss
|
102
105
|
- lib/generators/interview/install/templates/ckeditor_config.js
|
103
106
|
- lib/generators/interview/install/templates/colors.css.scss
|
@@ -123,6 +126,8 @@ files:
|
|
123
126
|
- lib/interview/attribute.rb
|
124
127
|
- lib/interview/boolean_attribute.rb
|
125
128
|
- lib/interview/breadcrumbs.rb
|
129
|
+
- lib/interview/collapse_container.rb
|
130
|
+
- lib/interview/condition_container.rb
|
126
131
|
- lib/interview/container_attribute.rb
|
127
132
|
- lib/interview/control.rb
|
128
133
|
- lib/interview/control_def.rb
|
@@ -138,17 +143,23 @@ files:
|
|
138
143
|
- lib/interview/html_control.rb
|
139
144
|
- lib/interview/html_text_attribute.rb
|
140
145
|
- lib/interview/image_attribute.rb
|
146
|
+
- lib/interview/image_gallery_attribute.rb
|
147
|
+
- lib/interview/image_light_box.rb
|
141
148
|
- lib/interview/integer_attribute.rb
|
142
149
|
- lib/interview/link.rb
|
143
150
|
- lib/interview/list.rb
|
151
|
+
- lib/interview/media_object.rb
|
144
152
|
- lib/interview/navigation.rb
|
145
153
|
- lib/interview/navigation_item.rb
|
154
|
+
- lib/interview/navigation_item_old.rb
|
146
155
|
- lib/interview/nested_form.rb
|
147
156
|
- lib/interview/nested_form_add_link.rb
|
148
157
|
- lib/interview/nested_form_remove_link.rb
|
149
158
|
- lib/interview/object_context.rb
|
150
159
|
- lib/interview/option_attribute.rb
|
160
|
+
- lib/interview/panel.rb
|
151
161
|
- lib/interview/polymorphic_add_link.rb
|
162
|
+
- lib/interview/search_form.rb
|
152
163
|
- lib/interview/space.rb
|
153
164
|
- lib/interview/string_attribute.rb
|
154
165
|
- lib/interview/tab.rb
|