arclight 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +6 -47
- data/.rubocop_todo.yml +259 -0
- data/.travis.yml +15 -20
- data/README.md +17 -4
- data/app/assets/images/blacklight/compact.svg +15 -15
- data/app/assets/javascripts/arclight/arclight.js +1 -0
- data/app/assets/javascripts/arclight/collection_navigation.js +5 -2
- data/app/assets/javascripts/arclight/oembed_viewer.js +11 -4
- data/app/assets/javascripts/arclight/search_results.js +15 -0
- data/app/assets/stylesheets/arclight/modules/hierarchy_and_online_contents.scss +6 -3
- data/app/assets/stylesheets/arclight/modules/layout.scss +24 -0
- data/app/assets/stylesheets/arclight/modules/mastheads.scss +33 -0
- data/app/helpers/arclight_helper.rb +1 -1
- data/app/models/concerns/arclight/search_behavior.rb +1 -1
- data/app/views/arclight/repositories/_in_person_repository.html.erb +1 -1
- data/app/views/catalog/_component_contents.html.erb +16 -0
- data/app/views/catalog/_component_overview.html.erb +0 -6
- data/app/views/catalog/_context_card.html.erb +1 -1
- data/app/views/catalog/_custom_metadata.html.erb +1 -1
- data/app/views/catalog/_index_default.html.erb +1 -1
- data/app/views/catalog/_index_header.html.erb +2 -2
- data/app/views/catalog/_index_header_hierarchy_default.html.erb +2 -2
- data/app/views/catalog/_index_hierarchy_default.html.erb +1 -1
- data/app/views/catalog/_results_histogram.html.erb +6 -1
- data/app/views/catalog/_show_breadcrumbs_default.html.erb +19 -5
- data/app/views/catalog/_show_default.html.erb +10 -0
- data/app/views/catalog/_show_sidebar.html.erb +0 -8
- data/app/views/catalog/_show_upper_metadata_collection.html.erb +1 -0
- data/app/views/catalog/_show_upper_metadata_default.html.erb +14 -0
- data/app/views/shared/_header_navbar.html.erb +56 -44
- data/app/views/shared/_main_menu_links.html.erb +1 -1
- data/arclight.gemspec +11 -7
- data/config/i18n-tasks.yml +132 -0
- data/config/locales/arclight.en.yml +53 -52
- data/lib/arclight/engine.rb +1 -0
- data/lib/arclight/hash_absolute_xpath.rb +57 -0
- data/lib/arclight/missing_id_strategy.rb +21 -0
- data/lib/arclight/normalized_date.rb +19 -10
- data/lib/arclight/repository.rb +3 -20
- data/lib/arclight/shared_indexing_behavior.rb +1 -1
- data/lib/arclight/solr_ead_indexer_ext.rb +5 -9
- data/lib/arclight/traject/ead2_config.rb +475 -0
- data/lib/arclight/version.rb +1 -1
- data/lib/generators/arclight/install_generator.rb +14 -0
- data/lib/generators/arclight/templates/catalog_controller.rb +43 -40
- data/lib/tasks/index.rake +4 -2
- data/solr/conf/schema.xml +7 -2
- data/tasks/arclight.rake +5 -1
- data/template.rb +1 -1
- metadata +94 -28
- data/app/views/catalog/_arclight_document_show_header.html.erb +0 -15
- data/app/views/catalog/_arclight_document_show_header_collection.html.erb +0 -12
- data/app/views/catalog/_search_within_form.html.erb +0 -16
- data/app/views/catalog/_show_header.html.erb +0 -5
@@ -1,6 +1,6 @@
|
|
1
1
|
<li class="nav-item <%= repositories_active_class %>">
|
2
2
|
<%= link_to t('arclight.routes.repositories'), arclight_engine.repositories_path, class: 'nav-link' %>
|
3
3
|
</li>
|
4
|
-
<li class="nav-item <%= collection_active_class %>">
|
4
|
+
<li class="nav-item ml-3 <%= collection_active_class %>">
|
5
5
|
<%= link_to t('arclight.routes.collections'), arclight_engine.collections_path, class: 'nav-link' %>
|
6
6
|
</li>
|
data/arclight.gemspec
CHANGED
@@ -23,19 +23,23 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
24
|
spec.require_paths = ['lib']
|
25
25
|
|
26
|
-
spec.add_dependency 'blacklight', '7.0.0.
|
27
|
-
spec.add_dependency 'blacklight_range_limit', '7.
|
26
|
+
spec.add_dependency 'blacklight', '~> 7.0', '>= 7.0.1'
|
27
|
+
spec.add_dependency 'blacklight_range_limit', '~> 7.1'
|
28
28
|
spec.add_dependency 'rails', '~> 5.0'
|
29
29
|
spec.add_dependency 'solr_ead'
|
30
|
+
spec.add_dependency 'traject', '~> 3.0'
|
31
|
+
spec.add_dependency 'traject_plus'
|
30
32
|
|
31
|
-
spec.add_development_dependency 'bundler', '
|
33
|
+
spec.add_development_dependency 'bundler', '> 1.14'
|
32
34
|
spec.add_development_dependency 'capybara'
|
33
|
-
spec.add_development_dependency 'coveralls'
|
34
35
|
spec.add_development_dependency 'engine_cart'
|
35
|
-
spec.add_development_dependency '
|
36
|
+
spec.add_development_dependency 'i18n-tasks'
|
36
37
|
spec.add_development_dependency 'rake', '~> 12.0'
|
37
|
-
spec.add_development_dependency 'rubocop', '~> 0.
|
38
|
-
spec.add_development_dependency 'rubocop-rspec', '~> 1.
|
38
|
+
spec.add_development_dependency 'rubocop', '~> 0.74.0'
|
39
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.35'
|
39
40
|
spec.add_development_dependency 'rspec-rails', '~> 3.0'
|
41
|
+
spec.add_development_dependency 'selenium-webdriver'
|
42
|
+
spec.add_development_dependency 'simplecov'
|
40
43
|
spec.add_development_dependency 'solr_wrapper'
|
44
|
+
spec.add_development_dependency 'webdrivers'
|
41
45
|
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# i18n-tasks finds and manages missing and unused translations: https://github.com/glebm/i18n-tasks
|
2
|
+
|
3
|
+
# The "main" locale.
|
4
|
+
base_locale: en
|
5
|
+
## All available locales are inferred from the data by default. Alternatively, specify them explicitly:
|
6
|
+
# locales: [es, fr]
|
7
|
+
## Reporting locale, default: en. Available: en, ru.
|
8
|
+
# internal_locale: en
|
9
|
+
|
10
|
+
# Read and write translations.
|
11
|
+
data:
|
12
|
+
## Translations are read from the file system. Supported format: YAML, JSON.
|
13
|
+
## Provide a custom adapter:
|
14
|
+
# adapter: I18n::Tasks::Data::FileSystem
|
15
|
+
|
16
|
+
# Locale files or `File.find` patterns where translations are read from:
|
17
|
+
read:
|
18
|
+
## Default:
|
19
|
+
# - config/locales/%{locale}.yml
|
20
|
+
## More files:
|
21
|
+
- config/locales/**/*.%{locale}.yml
|
22
|
+
|
23
|
+
# Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom:
|
24
|
+
# `i18n-tasks normalize -p` will force move the keys according to these rules
|
25
|
+
write:
|
26
|
+
## For example, write devise and simple form keys to their respective files:
|
27
|
+
# - ['{devise, simple_form}.*', 'config/locales/\1.%{locale}.yml']
|
28
|
+
## Catch-all default:
|
29
|
+
# - config/locales/%{locale}.yml
|
30
|
+
|
31
|
+
# External locale data (e.g. gems).
|
32
|
+
# This data is not considered unused and is never written to.
|
33
|
+
external:
|
34
|
+
## Example (replace %#= with %=):
|
35
|
+
# - "<%#= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml"
|
36
|
+
|
37
|
+
## Specify the router (see Readme for details). Valid values: conservative_router, pattern_router, or a custom class.
|
38
|
+
# router: conservative_router
|
39
|
+
|
40
|
+
yaml:
|
41
|
+
write:
|
42
|
+
# do not wrap lines at 80 characters
|
43
|
+
line_width: -1
|
44
|
+
|
45
|
+
## Pretty-print JSON:
|
46
|
+
# json:
|
47
|
+
# write:
|
48
|
+
# indent: ' '
|
49
|
+
# space: ' '
|
50
|
+
# object_nl: "\n"
|
51
|
+
# array_nl: "\n"
|
52
|
+
|
53
|
+
# Find translate calls
|
54
|
+
search:
|
55
|
+
## Paths or `File.find` patterns to search in:
|
56
|
+
# paths:
|
57
|
+
# - app/
|
58
|
+
|
59
|
+
## Root directories for relative keys resolution.
|
60
|
+
# relative_roots:
|
61
|
+
# - app/controllers
|
62
|
+
# - app/helpers
|
63
|
+
# - app/mailers
|
64
|
+
# - app/presenters
|
65
|
+
# - app/views
|
66
|
+
|
67
|
+
## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
|
68
|
+
## %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less *.yml *.json)
|
69
|
+
exclude:
|
70
|
+
- app/assets/images
|
71
|
+
- app/assets/fonts
|
72
|
+
- app/assets/videos
|
73
|
+
|
74
|
+
## Alternatively, the only files or `File.fnmatch patterns` to search in `paths`:
|
75
|
+
## If specified, this settings takes priority over `exclude`, but `exclude` still applies.
|
76
|
+
# only: ["*.rb", "*.html.slim"]
|
77
|
+
|
78
|
+
## If `strict` is `false`, guess usages such as t("categories.#{category}.title"). The default is `true`.
|
79
|
+
# strict: true
|
80
|
+
|
81
|
+
## Multiple scanners can be used. Their results are merged.
|
82
|
+
## The options specified above are passed down to each scanner. Per-scanner options can be specified as well.
|
83
|
+
## See this example of a custom scanner: https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example
|
84
|
+
|
85
|
+
## Translation Services
|
86
|
+
# translation:
|
87
|
+
# # Google Translate
|
88
|
+
# # Get an API key and set billing info at https://code.google.com/apis/console to use Google Translate
|
89
|
+
# google_translate_api_key: "AbC-dEf5"
|
90
|
+
# # DeepL Pro Translate
|
91
|
+
# # Get an API key and subscription at https://www.deepl.com/pro to use DeepL Pro
|
92
|
+
# deepl_api_key: "48E92789-57A3-466A-9959-1A1A1A1A1A1A"
|
93
|
+
|
94
|
+
## Do not consider these keys missing:
|
95
|
+
ignore_missing:
|
96
|
+
- '{blacklight}.*'
|
97
|
+
|
98
|
+
## Consider these keys used:
|
99
|
+
# ignore_unused:
|
100
|
+
# - 'activerecord.attributes.*'
|
101
|
+
# - '{devise,kaminari,will_paginate}.*'
|
102
|
+
# - 'simple_form.{yes,no}'
|
103
|
+
# - 'simple_form.{placeholders,hints,labels}.*'
|
104
|
+
# - 'simple_form.{error_notification,required}.:'
|
105
|
+
|
106
|
+
## Exclude these keys from the `i18n-tasks eq-base' report:
|
107
|
+
# ignore_eq_base:
|
108
|
+
# all:
|
109
|
+
# - common.ok
|
110
|
+
# fr,es:
|
111
|
+
# - common.brand
|
112
|
+
|
113
|
+
## Exclude these keys from the `i18n-tasks check-consistent-interpolations` report:
|
114
|
+
# ignore_inconsistent_interpolations:
|
115
|
+
# - 'activerecord.attributes.*'
|
116
|
+
|
117
|
+
## Ignore these keys completely:
|
118
|
+
# ignore:
|
119
|
+
# - kaminari.*
|
120
|
+
|
121
|
+
## Sometimes, it isn't possible for i18n-tasks to match the key correctly,
|
122
|
+
## e.g. in case of a relative key defined in a helper method.
|
123
|
+
## In these cases you can use the built-in PatternMapper to map patterns to keys, e.g.:
|
124
|
+
#
|
125
|
+
# <%# I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
|
126
|
+
# only: %w(*.html.haml *.html.slim),
|
127
|
+
# patterns: [['= title\b', '.page_title']] %>
|
128
|
+
#
|
129
|
+
# The PatternMapper can also match key literals via a special %{key} interpolation, e.g.:
|
130
|
+
#
|
131
|
+
# <%# I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
|
132
|
+
# patterns: [['\bSpree\.t[( ]\s*%{key}', 'spree.%{key}']] %>
|
@@ -1,65 +1,66 @@
|
|
1
|
+
---
|
1
2
|
en:
|
2
3
|
arclight:
|
3
|
-
|
4
|
+
breadcrumb_separator: " » "
|
4
5
|
date_range_histogram:
|
5
|
-
|
6
|
+
hide: Hide date distribution
|
7
|
+
show: Show date distribution
|
6
8
|
hierarchy:
|
7
|
-
|
8
|
-
|
9
|
+
scope_and_contents: Scope and Contents
|
10
|
+
view_all: View
|
11
|
+
masthead_heading: Archival Collections at Institution
|
9
12
|
request:
|
10
|
-
container:
|
13
|
+
container: Request
|
11
14
|
routes:
|
12
|
-
|
13
|
-
|
14
|
-
repositories:
|
15
|
-
search_results:
|
15
|
+
collections: Collections
|
16
|
+
home: Home
|
17
|
+
repositories: Repositories
|
18
|
+
search_results: Search results
|
19
|
+
truncation:
|
20
|
+
view_less: view less ▼
|
21
|
+
view_more: view more ▶
|
16
22
|
views:
|
17
23
|
index:
|
18
|
-
number_of_children:
|
19
|
-
zero: 'No children'
|
20
|
-
one: '1 child'
|
21
|
-
other: '%{count} children'
|
22
24
|
collection_search:
|
23
|
-
count:
|
24
|
-
|
25
|
+
count: collection
|
26
|
+
number_of_children:
|
27
|
+
one: 1 child
|
28
|
+
other: "%{count} children"
|
29
|
+
zero: No children
|
30
|
+
online_content_indicator: online content
|
31
|
+
repositories:
|
32
|
+
number_of_collections:
|
33
|
+
one: 1 collection
|
34
|
+
other: "%{count} collections"
|
35
|
+
zero: No collections
|
36
|
+
view_all_collections: View all of our collections
|
37
|
+
view_more: View more
|
25
38
|
show:
|
26
39
|
collection_id: 'Collection ID: %{id}'
|
27
|
-
overview: 'Overview'
|
28
|
-
online_content: 'Online content'
|
29
|
-
no_online_content: 'No online content'
|
30
|
-
no_contents: 'No content inventory'
|
31
|
-
search_within: 'Search within this collection'
|
32
|
-
navigation_sidebar:
|
33
|
-
title: 'Navigation overview'
|
34
40
|
context_sidebar:
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
sections:
|
40
|
-
summary_field: 'Summary'
|
41
|
-
access_field: 'Access and Use'
|
42
|
-
background_field: 'Background'
|
43
|
-
scope_and_arrangement_field: 'Scope and Arrangement'
|
44
|
-
related_field: 'Related'
|
45
|
-
indexed_terms_field: 'Indexed Terms'
|
46
|
-
admin_info_field: 'Administrative Information'
|
47
|
-
component_field: 'About this %{level}'
|
48
|
-
component_indexed_terms_field: 'Indexed Terms'
|
49
|
-
collection_context_field: 'Collection Context'
|
50
|
-
our_collections: 'Our Collections'
|
41
|
+
cite_field: How to cite this collection
|
42
|
+
component_terms_field: Terms & Conditions
|
43
|
+
in_person_field: In person
|
44
|
+
terms_field: Terms & Conditions
|
51
45
|
download:
|
52
|
-
default:
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
46
|
+
default: Download
|
47
|
+
ead: Collection EAD (%{size})
|
48
|
+
pdf: Collection PDF (%{size})
|
49
|
+
navigation_sidebar:
|
50
|
+
title: Navigation overview
|
51
|
+
no_contents: No content inventory
|
52
|
+
no_online_content: No online content
|
53
|
+
online_content: Online content
|
54
|
+
our_collections: Our Collections
|
55
|
+
overview: Overview
|
56
|
+
sections:
|
57
|
+
access_field: Access and Use
|
58
|
+
admin_info_field: Administrative Information
|
59
|
+
background_field: Background
|
60
|
+
collection_context_field: Collection Context
|
61
|
+
component_field: About this %{level}
|
62
|
+
component_indexed_terms_field: Indexed Terms
|
63
|
+
indexed_terms_field: Indexed Terms
|
64
|
+
related_field: Related
|
65
|
+
scope_and_arrangement_field: Scope and Arrangement
|
66
|
+
summary_field: Summary
|
data/lib/arclight/engine.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
module Arclight
|
6
|
+
##
|
7
|
+
# Take a Nokogiri node and get its absolute path (inserting our own indexes for component levels)
|
8
|
+
# and hash that outout. This is intended as a potential strategy for handling missing IDs in EADs.
|
9
|
+
class HashAbsoluteXpath
|
10
|
+
class << self
|
11
|
+
attr_writer :hash_algorithm
|
12
|
+
|
13
|
+
def hash_algorithm
|
14
|
+
return Digest::SHA1 unless defined? @hash_algorithm
|
15
|
+
|
16
|
+
@hash_algorithm
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
COMPONENT_NODE_NAME_REGEX = /^c\d{,2}$/.freeze
|
21
|
+
attr_reader :node
|
22
|
+
def initialize(node)
|
23
|
+
@node = node
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_hexdigest
|
27
|
+
self.class.hash_algorithm.hexdigest(absolute_xpath)
|
28
|
+
end
|
29
|
+
|
30
|
+
def absolute_xpath
|
31
|
+
ancestor_tree = node.ancestors.map do |ancestor|
|
32
|
+
if ancestor.name =~ COMPONENT_NODE_NAME_REGEX
|
33
|
+
index = component_siblings_for_node(ancestor).index(ancestor)
|
34
|
+
"#{ancestor.name}#{index}"
|
35
|
+
else
|
36
|
+
ancestor.name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
"#{[ancestor_tree.reverse, node.name].flatten.join('/')}#{current_index}"
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def current_index
|
46
|
+
siblings.index(node)
|
47
|
+
end
|
48
|
+
|
49
|
+
def component_siblings_for_node(xml_node)
|
50
|
+
xml_node.parent.children.select { |n| n.name =~ COMPONENT_NODE_NAME_REGEX }
|
51
|
+
end
|
52
|
+
|
53
|
+
def siblings
|
54
|
+
@siblings ||= component_siblings_for_node(node)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'arclight/hash_absolute_xpath'
|
4
|
+
|
5
|
+
module Arclight
|
6
|
+
##
|
7
|
+
# A class to configure a selected MissingIdStrategy.
|
8
|
+
# Defaults to Arclight::HashAbsoluteXpath
|
9
|
+
# This can be updated in an initializer to be any other class
|
10
|
+
class MissingIdStrategy
|
11
|
+
class << self
|
12
|
+
attr_writer :selected
|
13
|
+
|
14
|
+
def selected
|
15
|
+
return Arclight::HashAbsoluteXpath unless defined? @selected
|
16
|
+
|
17
|
+
@selected
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -7,16 +7,21 @@ module Arclight
|
|
7
7
|
# @see http://www2.archivists.org/standards/DACS/part_I/chapter_2/4_date
|
8
8
|
class NormalizedDate
|
9
9
|
# @param [String | Array<String>] `inclusive` from the `unitdate`
|
10
|
-
# @param [String] `bulk` from the `unitdate`
|
11
|
-
# @param [String] `other` from the `unitdate` when type is not specified
|
12
|
-
def initialize(inclusive, bulk =
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
10
|
+
# @param [Array<String>] `bulk` from the `unitdate`
|
11
|
+
# @param [Array<String>] `other` from the `unitdate` when type is not specified
|
12
|
+
def initialize(inclusive, bulk = [], other = [])
|
13
|
+
@inclusive = (inclusive || []).map do |inclusive_text|
|
14
|
+
if inclusive_text.is_a? Array # of YYYY-YYYY for ranges
|
15
|
+
# NOTE: This code is not routable AFAICT in actual indexing.
|
16
|
+
# We pass arrays of strings (or xml nodes) here, and never a multidimensional array
|
17
|
+
year_range(inclusive_text)
|
18
|
+
elsif inclusive_text.present?
|
19
|
+
inclusive_text.strip
|
20
|
+
end
|
21
|
+
end&.join(', ')
|
22
|
+
|
23
|
+
@bulk = Array.wrap(bulk).compact.map(&:strip).join(', ')
|
24
|
+
@other = Array.wrap(other).compact.map(&:strip).join(', ')
|
20
25
|
end
|
21
26
|
|
22
27
|
# @return [String] the normalized title/date
|
@@ -28,6 +33,10 @@ module Arclight
|
|
28
33
|
|
29
34
|
attr_reader :inclusive, :bulk, :other
|
30
35
|
|
36
|
+
def year_range(date_array)
|
37
|
+
YearRange.new(date_array.include?('/') ? date_array : date_array.map { |v| v.tr('-', '/') }).to_s
|
38
|
+
end
|
39
|
+
|
31
40
|
# @see http://www2.archivists.org/standards/DACS/part_I/chapter_2/4_date for rules
|
32
41
|
def normalize
|
33
42
|
if inclusive.present?
|
data/lib/arclight/repository.rb
CHANGED
@@ -7,31 +7,14 @@ module Arclight
|
|
7
7
|
class Repository
|
8
8
|
include ActiveModel::Conversion # for to_partial_path
|
9
9
|
|
10
|
-
|
11
|
-
description
|
12
|
-
visit_note
|
13
|
-
building
|
14
|
-
address1
|
15
|
-
address2
|
16
|
-
city
|
17
|
-
state
|
18
|
-
zip
|
19
|
-
country
|
20
|
-
phone
|
21
|
-
contact_info
|
22
|
-
thumbnail_url
|
23
|
-
google_request_url
|
24
|
-
google_request_mappings
|
25
|
-
collection_count].freeze
|
26
|
-
|
27
|
-
attr_accessor :slug, *FIELDS
|
10
|
+
attr_accessor :slug, :collection_count
|
28
11
|
|
29
12
|
# @param [String] `slug` the unique identifier for the repository
|
30
13
|
# @param [Hash] `data`
|
31
14
|
def initialize(slug, data = {})
|
32
15
|
@slug = slug
|
33
|
-
|
34
|
-
|
16
|
+
data.each do |field, value|
|
17
|
+
self.class.attr_accessor field.to_sym
|
35
18
|
send("#{field}=", value) if value.present?
|
36
19
|
end
|
37
20
|
end
|