license_finder 1.1.1-java → 1.2-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/CHANGELOG.rdoc +10 -0
- data/Gemfile +1 -1
- data/README.md +363 -0
- data/Rakefile +30 -1
- data/TODO.md +28 -0
- data/bin/license_finder_pip.py +18 -0
- data/db/migrate/201410031451_rename_dependency_license_name.rb +6 -0
- data/features/multiple_licenses.feature +9 -0
- data/features/step_definitions/cli_steps.rb +9 -9
- data/features/step_definitions/cocoapod_steps.rb +1 -1
- data/features/step_definitions/configure_bundler_groups_steps.rb +3 -3
- data/features/step_definitions/configure_whitelist_steps.rb +4 -4
- data/features/step_definitions/gradle_steps.rb +1 -1
- data/features/step_definitions/manually_added_steps.rb +3 -3
- data/features/step_definitions/manually_approved_steps.rb +5 -5
- data/features/step_definitions/manually_assigned_license_steps.rb +4 -4
- data/features/step_definitions/maven_steps.rb +1 -1
- data/features/step_definitions/multiple_licenses_steps.rb +14 -0
- data/features/step_definitions/node_steps.rb +1 -1
- data/features/step_definitions/python_steps.rb +1 -1
- data/features/step_definitions/report_csv_steps.rb +3 -3
- data/features/step_definitions/report_html_steps.rb +5 -5
- data/features/step_definitions/shared_steps.rb +23 -6
- data/lib/license_finder.rb +3 -0
- data/lib/license_finder/cli.rb +13 -34
- data/lib/license_finder/configuration.rb +8 -4
- data/lib/license_finder/dependency_manager.rb +25 -15
- data/lib/license_finder/license.rb +8 -0
- data/lib/license_finder/logger.rb +59 -0
- data/lib/license_finder/package.rb +37 -30
- data/lib/license_finder/package_manager.rb +20 -0
- data/lib/license_finder/package_managers/bower.rb +4 -9
- data/lib/license_finder/package_managers/bower_package.rb +2 -1
- data/lib/license_finder/package_managers/bundler.rb +26 -41
- data/lib/license_finder/package_managers/bundler_package.rb +6 -3
- data/lib/license_finder/package_managers/cocoa_pods.rb +18 -10
- data/lib/license_finder/package_managers/cocoa_pods_package.rb +4 -3
- data/lib/license_finder/package_managers/gradle.rb +7 -11
- data/lib/license_finder/package_managers/gradle_package.rb +2 -7
- data/lib/license_finder/package_managers/maven.rb +5 -9
- data/lib/license_finder/package_managers/maven_package.rb +4 -8
- data/lib/license_finder/package_managers/npm.rb +6 -10
- data/lib/license_finder/package_managers/npm_package.rb +2 -1
- data/lib/license_finder/package_managers/pip.rb +11 -24
- data/lib/license_finder/package_managers/pip_package.rb +2 -1
- data/lib/license_finder/package_saver.rb +2 -2
- data/lib/license_finder/platform.rb +4 -0
- data/lib/license_finder/possible_license_file.rb +4 -0
- data/lib/license_finder/possible_license_files.rb +2 -1
- data/lib/license_finder/reports/detailed_text_report.rb +1 -1
- data/lib/license_finder/reports/formatted_report.rb +1 -1
- data/lib/license_finder/tables/dependency.rb +22 -12
- data/lib/license_finder/yml_to_sql.rb +1 -1
- data/lib/templates/html_report.erb +4 -4
- data/lib/templates/markdown_report.erb +4 -4
- data/lib/templates/text_report.erb +1 -1
- data/license_finder.gemspec +28 -12
- data/spec/lib/license_finder/cli_spec.rb +193 -185
- data/spec/lib/license_finder/configuration_spec.rb +46 -47
- data/spec/lib/license_finder/dependency_manager_spec.rb +48 -44
- data/spec/lib/license_finder/license/definitions_spec.rb +26 -26
- data/spec/lib/license_finder/license_spec.rb +25 -25
- data/spec/lib/license_finder/package_managers/bower_package_spec.rb +33 -17
- data/spec/lib/license_finder/package_managers/bower_spec.rb +35 -35
- data/spec/lib/license_finder/package_managers/bundler_package_spec.rb +20 -15
- data/spec/lib/license_finder/package_managers/bundler_spec.rb +12 -19
- data/spec/lib/license_finder/package_managers/cocoa_pods_package_spec.rb +8 -5
- data/spec/lib/license_finder/package_managers/cocoa_pods_spec.rb +20 -22
- data/spec/lib/license_finder/package_managers/gradle_package_spec.rb +8 -5
- data/spec/lib/license_finder/package_managers/gradle_spec.rb +20 -20
- data/spec/lib/license_finder/package_managers/maven_package_spec.rb +8 -5
- data/spec/lib/license_finder/package_managers/maven_spec.rb +18 -18
- data/spec/lib/license_finder/package_managers/npm_package_spec.rb +36 -17
- data/spec/lib/license_finder/package_managers/npm_spec.rb +17 -17
- data/spec/lib/license_finder/package_managers/pip_package_spec.rb +16 -10
- data/spec/lib/license_finder/package_managers/pip_spec.rb +21 -18
- data/spec/lib/license_finder/package_saver_spec.rb +15 -25
- data/spec/lib/license_finder/possible_license_file_spec.rb +5 -4
- data/spec/lib/license_finder/possible_license_files_spec.rb +11 -5
- data/spec/lib/license_finder/reports/detailed_text_report_spec.rb +3 -3
- data/spec/lib/license_finder/reports/html_report_spec.rb +23 -23
- data/spec/lib/license_finder/reports/markdown_report_spec.rb +12 -12
- data/spec/lib/license_finder/reports/reporter_spec.rb +11 -11
- data/spec/lib/license_finder/reports/text_report_spec.rb +3 -3
- data/spec/lib/license_finder/tables/dependency_spec.rb +59 -41
- data/spec/lib/license_finder/yml_to_sql_spec.rb +21 -21
- data/spec/lib/license_finder_spec.rb +1 -1
- data/spec/spec_helper.rb +0 -13
- data/spec/support/shared_examples_for_package.rb +46 -0
- data/spec/support/shared_examples_for_package_manager.rb +15 -0
- metadata +19 -100
- data/readme.md +0 -259
|
@@ -4,12 +4,11 @@ module LicenseFinder
|
|
|
4
4
|
def_delegators :gem_def, :summary, :description, :name, :homepage
|
|
5
5
|
|
|
6
6
|
attr_reader :gem_def
|
|
7
|
-
attr_accessor :children
|
|
8
7
|
|
|
9
|
-
def initialize(gem_def, bundler_def)
|
|
8
|
+
def initialize(gem_def, bundler_def, options={})
|
|
9
|
+
super options
|
|
10
10
|
@gem_def = gem_def
|
|
11
11
|
@bundler_def = bundler_def
|
|
12
|
-
@children = []
|
|
13
12
|
end
|
|
14
13
|
|
|
15
14
|
def groups
|
|
@@ -20,6 +19,10 @@ module LicenseFinder
|
|
|
20
19
|
gem_def.version.to_s
|
|
21
20
|
end
|
|
22
21
|
|
|
22
|
+
def children
|
|
23
|
+
gem_def.dependencies.map(&:name)
|
|
24
|
+
end
|
|
25
|
+
|
|
23
26
|
private
|
|
24
27
|
|
|
25
28
|
def install_path
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
|
|
3
3
|
module LicenseFinder
|
|
4
|
-
class CocoaPods
|
|
5
|
-
|
|
6
|
-
def self.current_packages
|
|
4
|
+
class CocoaPods < PackageManager
|
|
5
|
+
def current_packages
|
|
7
6
|
podfile = YAML.load_file(lockfile_path)
|
|
8
7
|
|
|
9
|
-
acknowledgements =
|
|
8
|
+
acknowledgements = read_plist(acknowledgements_path)["PreferenceSpecifiers"]
|
|
10
9
|
|
|
11
10
|
podfile["PODS"].map do |pod|
|
|
12
11
|
pod = pod.keys.first if pod.is_a?(Hash)
|
|
@@ -17,19 +16,28 @@ module LicenseFinder
|
|
|
17
16
|
end
|
|
18
17
|
end
|
|
19
18
|
|
|
20
|
-
def self.active?
|
|
21
|
-
package_path.exist?
|
|
22
|
-
end
|
|
23
|
-
|
|
24
19
|
private
|
|
25
20
|
|
|
26
|
-
def
|
|
21
|
+
def package_path
|
|
27
22
|
Pathname.new("Podfile")
|
|
28
23
|
end
|
|
29
24
|
|
|
30
|
-
def
|
|
25
|
+
def lockfile_path
|
|
31
26
|
Pathname.new("Podfile.lock")
|
|
32
27
|
end
|
|
33
28
|
|
|
29
|
+
def acknowledgements_path
|
|
30
|
+
filename = 'Pods-acknowledgements.plist'
|
|
31
|
+
directories = [
|
|
32
|
+
'Pods', # cocoapods < 0.34
|
|
33
|
+
'Pods/Target Support Files/Pods' # cocoapods >= 0.34
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
directories.map { |dir| Pathname.new(File.join(dir, filename)) }.find(&:exist?)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def read_plist pathname
|
|
40
|
+
JSON.parse(`plutil -convert json -o - '#{pathname.expand_path}'`)
|
|
41
|
+
end
|
|
34
42
|
end
|
|
35
43
|
end
|
|
@@ -3,7 +3,8 @@ module LicenseFinder
|
|
|
3
3
|
attr_reader :name, :version
|
|
4
4
|
attr_reader :summary, :description, :homepage
|
|
5
5
|
|
|
6
|
-
def initialize(name, version, license_text)
|
|
6
|
+
def initialize(name, version, license_text, options={})
|
|
7
|
+
super options
|
|
7
8
|
@name = name
|
|
8
9
|
@version = version
|
|
9
10
|
@license_text = license_text
|
|
@@ -12,8 +13,8 @@ module LicenseFinder
|
|
|
12
13
|
def groups; []; end
|
|
13
14
|
def children; []; end
|
|
14
15
|
|
|
15
|
-
def
|
|
16
|
-
License.find_by_text(@license_text.to_s) || default_license
|
|
16
|
+
def licenses
|
|
17
|
+
[License.find_by_text(@license_text.to_s) || default_license].to_set
|
|
17
18
|
end
|
|
18
19
|
end
|
|
19
20
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
require "xmlsimple"
|
|
2
2
|
|
|
3
3
|
module LicenseFinder
|
|
4
|
-
class Gradle
|
|
5
|
-
def
|
|
4
|
+
class Gradle < PackageManager
|
|
5
|
+
def current_packages
|
|
6
6
|
`#{LicenseFinder.config.gradle_command} downloadLicenses`
|
|
7
7
|
|
|
8
8
|
xml = license_report.read
|
|
@@ -10,23 +10,19 @@ module LicenseFinder
|
|
|
10
10
|
options = {
|
|
11
11
|
'GroupTags' => { 'dependencies' => 'dependency' }
|
|
12
12
|
}
|
|
13
|
-
XmlSimple.xml_in(xml, options).fetch('dependency', []).map do |
|
|
14
|
-
|
|
15
|
-
GradlePackage.new(
|
|
13
|
+
XmlSimple.xml_in(xml, options).fetch('dependency', []).map do |dep|
|
|
14
|
+
dep["license"].reject! { |l| l["name"] == "No license found" }
|
|
15
|
+
GradlePackage.new(dep, logger: logger)
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def self.active?
|
|
20
|
-
package_path.exist?
|
|
21
|
-
end
|
|
22
|
-
|
|
23
19
|
private
|
|
24
20
|
|
|
25
|
-
def
|
|
21
|
+
def license_report
|
|
26
22
|
Pathname.new('build/reports/license/dependency-license.xml')
|
|
27
23
|
end
|
|
28
24
|
|
|
29
|
-
def
|
|
25
|
+
def package_path
|
|
30
26
|
Pathname.new('build.gradle')
|
|
31
27
|
end
|
|
32
28
|
end
|
|
@@ -2,7 +2,8 @@ module LicenseFinder
|
|
|
2
2
|
class GradlePackage < Package
|
|
3
3
|
attr_reader :name, :version
|
|
4
4
|
|
|
5
|
-
def initialize(gradle_dependency)
|
|
5
|
+
def initialize(gradle_dependency, options={})
|
|
6
|
+
super options
|
|
6
7
|
@gradle_dependency = gradle_dependency
|
|
7
8
|
@name = @gradle_dependency["name"].split(":")[1]
|
|
8
9
|
@version = @gradle_dependency["name"].split(":")[2]
|
|
@@ -28,12 +29,6 @@ module LicenseFinder
|
|
|
28
29
|
[]
|
|
29
30
|
end
|
|
30
31
|
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def licenses_from_files
|
|
34
|
-
[]
|
|
35
|
-
end
|
|
36
|
-
|
|
37
32
|
def license_names_from_spec
|
|
38
33
|
@gradle_dependency["license"].map { |l| l["name"] }
|
|
39
34
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
require "xmlsimple"
|
|
2
2
|
|
|
3
3
|
module LicenseFinder
|
|
4
|
-
class Maven
|
|
5
|
-
def
|
|
4
|
+
class Maven < PackageManager
|
|
5
|
+
def current_packages
|
|
6
6
|
`mvn license:download-licenses`
|
|
7
7
|
|
|
8
8
|
xml = license_report.read
|
|
@@ -14,21 +14,17 @@ module LicenseFinder
|
|
|
14
14
|
dependencies = XmlSimple.xml_in(xml, options)["dependencies"]
|
|
15
15
|
|
|
16
16
|
dependencies.map do |dep|
|
|
17
|
-
MavenPackage.new(dep)
|
|
17
|
+
MavenPackage.new(dep, logger: logger)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def self.active?
|
|
22
|
-
package_path.exist?
|
|
23
|
-
end
|
|
24
|
-
|
|
25
21
|
private
|
|
26
22
|
|
|
27
|
-
def
|
|
23
|
+
def license_report
|
|
28
24
|
Pathname.new('target/generated-resources/licenses.xml')
|
|
29
25
|
end
|
|
30
26
|
|
|
31
|
-
def
|
|
27
|
+
def package_path
|
|
32
28
|
Pathname.new('pom.xml')
|
|
33
29
|
end
|
|
34
30
|
end
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
module LicenseFinder
|
|
2
2
|
class MavenPackage < Package
|
|
3
|
-
|
|
3
|
+
attr_reader :mvn_dependency
|
|
4
|
+
|
|
5
|
+
def initialize(mvn_dependency, options={})
|
|
6
|
+
super options
|
|
4
7
|
@mvn_dependency = mvn_dependency
|
|
5
8
|
end
|
|
6
9
|
|
|
@@ -32,13 +35,6 @@ module LicenseFinder
|
|
|
32
35
|
[]
|
|
33
36
|
end
|
|
34
37
|
|
|
35
|
-
private
|
|
36
|
-
attr_reader :mvn_dependency
|
|
37
|
-
|
|
38
|
-
def licenses_from_files
|
|
39
|
-
[]
|
|
40
|
-
end
|
|
41
|
-
|
|
42
38
|
def license_names_from_spec
|
|
43
39
|
mvn_dependency["licenses"].map { |l| l["name"] }
|
|
44
40
|
end
|
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
require 'json'
|
|
2
2
|
|
|
3
3
|
module LicenseFinder
|
|
4
|
-
class NPM
|
|
4
|
+
class NPM < PackageManager
|
|
5
5
|
DEPENDENCY_GROUPS = ["dependencies", "devDependencies", "bundleDependencies", "bundledDependencies"]
|
|
6
6
|
|
|
7
|
-
def
|
|
7
|
+
def current_packages
|
|
8
8
|
json = npm_json
|
|
9
9
|
dependencies = DEPENDENCY_GROUPS.map { |g| (json[g] || {}).values }.flatten(1).reject{ |d| d.is_a?(String) }
|
|
10
10
|
|
|
11
11
|
dependencies.map do |node_module|
|
|
12
|
-
NpmPackage.new(node_module)
|
|
12
|
+
NpmPackage.new(node_module, logger: logger)
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def self.active?
|
|
17
|
-
package_path.exist?
|
|
18
|
-
end
|
|
19
|
-
|
|
20
16
|
private
|
|
21
17
|
|
|
22
|
-
def
|
|
18
|
+
def npm_json
|
|
23
19
|
command = "npm list --json --long"
|
|
24
20
|
output, success = capture(command)
|
|
25
21
|
if success
|
|
@@ -35,11 +31,11 @@ module LicenseFinder
|
|
|
35
31
|
json
|
|
36
32
|
end
|
|
37
33
|
|
|
38
|
-
def
|
|
34
|
+
def capture(command)
|
|
39
35
|
[`#{command}`, $?.success?]
|
|
40
36
|
end
|
|
41
37
|
|
|
42
|
-
def
|
|
38
|
+
def package_path
|
|
43
39
|
Pathname.new('package.json')
|
|
44
40
|
end
|
|
45
41
|
end
|
|
@@ -2,40 +2,27 @@ require 'json'
|
|
|
2
2
|
require 'httparty'
|
|
3
3
|
|
|
4
4
|
module LicenseFinder
|
|
5
|
-
class Pip
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
dists = [(x.project_name, x.version, x.location) for x in get_installed_distributions()]
|
|
10
|
-
dists = ["[\\\"{0}\\\", \\\"{1}\\\", \\\"{2}\\\"]".format(*dist) for dist in dists]
|
|
11
|
-
|
|
12
|
-
print "[" + ",".join(dists) + "]"
|
|
13
|
-
PYTHON
|
|
14
|
-
|
|
15
|
-
def self.current_packages
|
|
16
|
-
output = `python -c '#{GET_DEPENDENCIES_PY}'`
|
|
17
|
-
|
|
18
|
-
JSON(output).map do |(name, version, install_dir)|
|
|
5
|
+
class Pip < PackageManager
|
|
6
|
+
def current_packages
|
|
7
|
+
output = `#{LicenseFinder::BIN_PATH.join("license_finder_pip.py")}`
|
|
8
|
+
JSON(output).map do |package|
|
|
19
9
|
PipPackage.new(
|
|
20
|
-
name,
|
|
21
|
-
version,
|
|
22
|
-
File.join(
|
|
23
|
-
pypi_def(name, version)
|
|
10
|
+
package["name"],
|
|
11
|
+
package["version"],
|
|
12
|
+
File.join(package["location"], package["name"]),
|
|
13
|
+
pypi_def(package["name"], package["version"]),
|
|
14
|
+
logger: logger
|
|
24
15
|
)
|
|
25
16
|
end
|
|
26
17
|
end
|
|
27
18
|
|
|
28
|
-
def self.active?
|
|
29
|
-
requirements_path.exist?
|
|
30
|
-
end
|
|
31
|
-
|
|
32
19
|
private
|
|
33
20
|
|
|
34
|
-
def
|
|
21
|
+
def package_path
|
|
35
22
|
Pathname.new('requirements.txt')
|
|
36
23
|
end
|
|
37
24
|
|
|
38
|
-
def
|
|
25
|
+
def pypi_def(name, version)
|
|
39
26
|
response = HTTParty.get("https://pypi.python.org/pypi/#{name}/#{version}/json")
|
|
40
27
|
if response.code == 200
|
|
41
28
|
JSON.parse(response.body).fetch("info", {})
|
|
@@ -3,7 +3,7 @@ require 'forwardable'
|
|
|
3
3
|
module LicenseFinder
|
|
4
4
|
class PackageSaver
|
|
5
5
|
extend Forwardable
|
|
6
|
-
def_delegators :package, :
|
|
6
|
+
def_delegators :package, :licenses, :children, :groups, :summary, :description, :version, :homepage
|
|
7
7
|
|
|
8
8
|
attr_reader :dependency, :package
|
|
9
9
|
|
|
@@ -25,7 +25,7 @@ module LicenseFinder
|
|
|
25
25
|
dependency.homepage = homepage
|
|
26
26
|
dependency.bundler_group_names = groups.map(&:to_s)
|
|
27
27
|
dependency.children_names = children
|
|
28
|
-
dependency.
|
|
28
|
+
dependency.set_licenses licenses
|
|
29
29
|
|
|
30
30
|
# Only save *changed* dependencies. This ensures re-running
|
|
31
31
|
# `license_finder` won't always update the DB, and therefore won't always
|
|
@@ -8,7 +8,7 @@ module LicenseFinder
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def initialize(install_path)
|
|
11
|
-
@install_path = Pathname(install_path)
|
|
11
|
+
@install_path = install_path ? Pathname(install_path) : nil
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def find
|
|
@@ -28,6 +28,7 @@ module LicenseFinder
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def candidate_files_and_dirs
|
|
31
|
+
return [] if install_path.nil?
|
|
31
32
|
Pathname.glob(install_path.join('**', CANDIDATE_PATH_WILDCARD))
|
|
32
33
|
end
|
|
33
34
|
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
1
3
|
module LicenseFinder
|
|
2
4
|
class Dependency < Sequel::Model
|
|
3
5
|
plugin :boolean_readers
|
|
4
6
|
plugin :composition
|
|
5
|
-
composition :
|
|
6
|
-
composer: ->(d)
|
|
7
|
-
|
|
7
|
+
composition :licenses,
|
|
8
|
+
composer: ->(d) do
|
|
9
|
+
if d.license_names.nil?
|
|
10
|
+
[License.find_by_name(nil)].to_set
|
|
11
|
+
else
|
|
12
|
+
names = JSON.parse(d.license_names)
|
|
13
|
+
names.map { |n| License.find_by_name(n) }.to_set
|
|
14
|
+
end
|
|
15
|
+
end,
|
|
16
|
+
decomposer: ->(d) { self.license_names = licenses.map(&:name).to_json }
|
|
8
17
|
|
|
9
18
|
one_to_one :manual_approval
|
|
10
19
|
many_to_many :children, join_table: :ancestries, left_key: :parent_dependency_id, right_key: :child_dependency_id, class: self
|
|
@@ -58,26 +67,27 @@ module LicenseFinder
|
|
|
58
67
|
end
|
|
59
68
|
|
|
60
69
|
def whitelisted?
|
|
61
|
-
|
|
70
|
+
licenses.any? &:whitelisted?
|
|
62
71
|
end
|
|
63
72
|
|
|
64
73
|
def approved_manually?
|
|
65
74
|
!!manual_approval
|
|
66
75
|
end
|
|
67
76
|
|
|
77
|
+
def set_licenses(other_licenses)
|
|
78
|
+
return if license_assigned_manually?
|
|
79
|
+
other_licenses = other_licenses.to_set
|
|
80
|
+
if licenses != other_licenses
|
|
81
|
+
self.licenses = other_licenses
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
68
85
|
def set_license_manually!(license)
|
|
69
|
-
self.
|
|
86
|
+
self.licenses = [license].to_set
|
|
70
87
|
self.license_assigned_manually = true
|
|
71
88
|
save
|
|
72
89
|
end
|
|
73
90
|
|
|
74
|
-
def apply_better_license(other_license)
|
|
75
|
-
return if license_assigned_manually?
|
|
76
|
-
if license.name != other_license.name
|
|
77
|
-
self.license = other_license
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
91
|
private
|
|
82
92
|
|
|
83
93
|
def update_association_collection(association_name, names)
|