ossert 0.1.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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/.rubocop_todo.yml +44 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +16 -0
  8. data/Gemfile +8 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +199 -0
  11. data/Rakefile +12 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/config/classifiers.yml +153 -0
  15. data/config/descriptions.yml +45 -0
  16. data/config/sidekiq.rb +15 -0
  17. data/config/stats.yml +198 -0
  18. data/config/translations.yml +44 -0
  19. data/db/backups/.keep +0 -0
  20. data/db/migrate/001_create_projects.rb +22 -0
  21. data/db/migrate/002_create_exceptions.rb +14 -0
  22. data/db/migrate/003_add_meta_to_projects.rb +14 -0
  23. data/db/migrate/004_add_timestamps_to_projects.rb +12 -0
  24. data/db/migrate/005_create_classifiers.rb +19 -0
  25. data/lib/ossert/classifiers/decision_tree.rb +112 -0
  26. data/lib/ossert/classifiers/growing/check.rb +172 -0
  27. data/lib/ossert/classifiers/growing/classifier.rb +175 -0
  28. data/lib/ossert/classifiers/growing.rb +163 -0
  29. data/lib/ossert/classifiers.rb +14 -0
  30. data/lib/ossert/config.rb +24 -0
  31. data/lib/ossert/fetch/bestgems.rb +98 -0
  32. data/lib/ossert/fetch/github.rb +536 -0
  33. data/lib/ossert/fetch/rubygems.rb +80 -0
  34. data/lib/ossert/fetch.rb +142 -0
  35. data/lib/ossert/presenters/project.rb +202 -0
  36. data/lib/ossert/presenters/project_v2.rb +117 -0
  37. data/lib/ossert/presenters.rb +8 -0
  38. data/lib/ossert/project.rb +144 -0
  39. data/lib/ossert/quarters_store.rb +164 -0
  40. data/lib/ossert/rake_tasks.rb +6 -0
  41. data/lib/ossert/reference.rb +87 -0
  42. data/lib/ossert/repositories.rb +138 -0
  43. data/lib/ossert/saveable.rb +153 -0
  44. data/lib/ossert/stats/agility_quarter.rb +62 -0
  45. data/lib/ossert/stats/agility_total.rb +71 -0
  46. data/lib/ossert/stats/base.rb +113 -0
  47. data/lib/ossert/stats/community_quarter.rb +28 -0
  48. data/lib/ossert/stats/community_total.rb +24 -0
  49. data/lib/ossert/stats.rb +32 -0
  50. data/lib/ossert/tasks/database.rake +179 -0
  51. data/lib/ossert/tasks/ossert.rake +52 -0
  52. data/lib/ossert/version.rb +4 -0
  53. data/lib/ossert/workers/fetch.rb +21 -0
  54. data/lib/ossert/workers/fetch_bestgems_page.rb +32 -0
  55. data/lib/ossert/workers/refresh_fetch.rb +22 -0
  56. data/lib/ossert/workers/sync_rubygems.rb +0 -0
  57. data/lib/ossert/workers.rb +11 -0
  58. data/lib/ossert.rb +63 -0
  59. data/ossert.gemspec +47 -0
  60. metadata +396 -0
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ module Stats
4
+ class Base
5
+ class << self
6
+ attr_accessor :section, :section_type
7
+
8
+ def config
9
+ @config ||= ::Settings['stats'][section][section_type]
10
+ end
11
+
12
+ def attributes
13
+ @attributes ||= config['attributes']
14
+ end
15
+
16
+ def uniq_attributes
17
+ @uniq_attributes ||= config['uniq_attributes'].to_a
18
+ end
19
+
20
+ def absolute_attributes
21
+ @absolute_attributes ||= config['absolute_attributes'].to_a
22
+ end
23
+
24
+ def attributes_names
25
+ @attributes_names ||= attributes.keys
26
+ end
27
+
28
+ def metrics
29
+ @metrics ||= config['metrics']
30
+ end
31
+
32
+ def create_attributes_accessors
33
+ attr_accessor(*attributes_names)
34
+ end
35
+
36
+ def define_ints(*attributes)
37
+ Array.wrap(attributes).each do |metric|
38
+ define_method("#{metric}_int") { public_send(metric).to_i }
39
+ end
40
+ end
41
+
42
+ def define_counts(*attributes)
43
+ Array.wrap(attributes).each do |metric|
44
+ define_method("#{metric}_count") { public_send(metric).count }
45
+ end
46
+ end
47
+
48
+ def define_percent(attributes, default_value: 0)
49
+ attributes.to_h.each do |metric, total|
50
+ define_method("#{metric}_percent") do
51
+ total_count = public_send(total).count
52
+ return default_value if total_count.zero?
53
+
54
+ ((public_send(metric).count.to_d / total_count.to_d) * 100).round(2)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def <<(other_stats)
61
+ self.class.attributes_names.each do |attr|
62
+ next unless other_value = other_stats.instance_variable_get("@#{attr}")
63
+ new_value = other_value
64
+ new_value += instance_variable_get("@#{attr}") unless self.class.absolute_attributes.include?(attr)
65
+ new_value.uniq! if self.class.uniq_attributes.include?(attr)
66
+
67
+ instance_variable_set("@#{attr}", new_value)
68
+ end
69
+ self
70
+ end
71
+
72
+ def initialize
73
+ self.class.attributes.each do |var, type|
74
+ instance_variable_set("@#{var}", Kernel.const_get(type).new) if type
75
+ end
76
+ end
77
+
78
+ def median(values, default_value: 0)
79
+ values = Array(values).sort
80
+ return default_value if (count = values.count).zero?
81
+
82
+ middle_idx = values.count / 2
83
+ return values[middle_idx] if count.odd?
84
+
85
+ (values[middle_idx - 1] + values[middle_idx]) / 2
86
+ end
87
+
88
+ def metric_values
89
+ self.class.metrics.map { |metric| public_send(metric).to_f }
90
+ end
91
+
92
+ def metrics_to_hash
93
+ self.class.metrics.each_with_object({}) do |var, result|
94
+ value = send(var)
95
+ value.uniq! if self.class.uniq_attributes.include?(var)
96
+ result[var] = value
97
+ end
98
+ end
99
+
100
+ def to_hash
101
+ self.class.attributes_names.each_with_object({}) do |var, result|
102
+ value = send(var)
103
+ value.uniq! if self.class.uniq_attributes.include?(var)
104
+ result[var] = value
105
+ end
106
+ end
107
+
108
+ def to_json
109
+ MultiJson.dump(self)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ module Stats
4
+ class CommunityQuarter < Base
5
+ self.section = 'community'
6
+ self.section_type = 'quarter'
7
+ create_attributes_accessors
8
+
9
+ define_counts(
10
+ :users_creating_issues, :users_commenting_issues, :users_creating_pr,
11
+ :users_commenting_pr, :contributors, :stargazers, :forks,
12
+ :users_involved
13
+ )
14
+
15
+ def users_involved_no_stars_count
16
+ (users_involved - stargazers).count
17
+ end
18
+
19
+ def total_downloads_count
20
+ delta_downloads
21
+ end
22
+
23
+ def download_divergence
24
+ (delta_downloads.to_f / (1 + total_downloads.to_f)) * 100.0
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ module Stats
4
+ class CommunityTotal < Base
5
+ self.section = 'community'
6
+ self.section_type = 'total'
7
+ create_attributes_accessors
8
+
9
+ define_counts(
10
+ :users_creating_issues, :users_commenting_issues, :users_creating_pr,
11
+ :users_commenting_pr, :contributors, :watchers, :stargazers, :forks,
12
+ :users_involved, :dependants
13
+ )
14
+
15
+ def users_involved_no_stars_count
16
+ (users_involved - stargazers).count
17
+ end
18
+
19
+ def total_downloads_count
20
+ total_downloads
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ require 'ossert/stats/base'
3
+ require 'ossert/stats/agility_total'
4
+ require 'ossert/stats/agility_quarter'
5
+ require 'ossert/stats/community_total'
6
+ require 'ossert/stats/community_quarter'
7
+
8
+ module Ossert
9
+ module Stats
10
+ PER_QUARTER_TOO_LONG = (5.years / 1.day).to_i
11
+ PER_YEAR_TOO_LONG = PER_QUARTER_TOO_LONG / 4
12
+
13
+ def guess_section_by_metric(metric)
14
+ found_section = :not_found
15
+ section_by_metric.each do |section, metrics|
16
+ next unless metrics.include? metric
17
+ found_section = section
18
+ break
19
+ end
20
+ found_section
21
+ end
22
+ module_function :guess_section_by_metric
23
+
24
+ def section_by_metric
25
+ @section_by_metric ||= {
26
+ agility: AgilityTotal.metrics + AgilityQuarter.metrics,
27
+ community: CommunityTotal.metrics + CommunityQuarter.metrics
28
+ }
29
+ end
30
+ module_function :section_by_metric
31
+ end
32
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+ namespace :db do
3
+ require 'sequel'
4
+ Sequel.extension :migration
5
+
6
+ namespace :test do
7
+ task :prepare do
8
+ test_database_url = ENV.fetch('TEST_DATABASE_URL')
9
+ database_name = test_database_url.split('/').last
10
+
11
+ DB = Sequel.connect(test_database_url)
12
+
13
+ sh "dropdb #{database_name}" do
14
+ # Ignore errors
15
+ end
16
+
17
+ sh "createdb #{database_name}" do
18
+ # Ignore errors
19
+ end
20
+
21
+ Sequel::Migrator.run(DB, File.expand_path('../../../../db/migrate', __FILE__))
22
+ Rake::Task['db:version'].execute
23
+ end
24
+ end
25
+
26
+ task :migrate => :load_config do
27
+ Sequel::Migrator.run(DB, File.expand_path('../../../../db/migrate', __FILE__))
28
+ Rake::Task['db:version'].execute
29
+ end
30
+
31
+ desc 'Prints current schema version'
32
+ task :version do
33
+ version = if DB.tables.include?(:schema_info)
34
+ DB[:schema_info].first[:version]
35
+ end || 0
36
+
37
+ puts "Schema Version: #{version}"
38
+ end
39
+
40
+ task :load_config do
41
+ DB = Sequel.connect(ENV.fetch('DATABASE_URL'))
42
+ end
43
+
44
+ desc 'Create the database, load the schema, and initialize with the seed data (db:reset to also drop the db first)'
45
+ task :setup do
46
+ Rake::Task['db:create'].invoke
47
+ Rake::Task['db:load_config'].invoke
48
+
49
+ Sequel::Migrator.run(DB, File.expand_path('../../../../db/migrate', __FILE__))
50
+ Rake::Task['db:version'].execute
51
+ end
52
+
53
+ task :drop do
54
+ sh "dropdb #{ENV.fetch('DATABASE_URL').split('/').last}" do
55
+ # Ignore errors
56
+ end
57
+ end
58
+
59
+ task :create do
60
+ sh "createdb #{ENV.fetch('DATABASE_URL').split('/').last}" do
61
+ # Ignore errors
62
+ end
63
+ end
64
+
65
+ desc 'Dumps the database to backups'
66
+ task :dump, [:fmt] do |_, args|
67
+ dump_fmt = args.fmt || 'c' # or 'p', 't', 'd'
68
+ dump_sfx = suffix_for_format dump_fmt
69
+ backup_dir = backup_directory true
70
+ cmd = nil
71
+ with_config do |app, db_url|
72
+ file_name = Time.now.strftime('%Y%m%d%H%M%S') + '_' + app + '_db.' + dump_sfx
73
+ cmd = "pg_dump #{db_url} --no-owner --no-acl -F #{dump_fmt} -v -f #{backup_dir}/#{file_name}"
74
+ end
75
+ puts cmd
76
+ sh cmd do
77
+ # Ignore errors
78
+ end
79
+ end
80
+
81
+ desc 'Show the existing database backups'
82
+ task :list_backups do
83
+ backup_dir = backup_directory
84
+ puts backup_dir.to_s
85
+ exec "/bin/ls -lht #{backup_dir}"
86
+ end
87
+
88
+ desc 'Restores the database from a backup using PATTERN'
89
+ task :restore, [:pat] do |_, args|
90
+ puts 'Please pass a pattern to the task' unless args.pat.present?
91
+ cmd = nil
92
+ with_config do |_, db_url|
93
+ cmd = command_for_files args.pat, db_url
94
+ end
95
+ unless cmd.nil?
96
+ Rake::Task['db:drop'].invoke
97
+ Rake::Task['db:create'].invoke
98
+ puts cmd
99
+ exec "#{cmd} || exit 0"
100
+ end
101
+ end
102
+
103
+ def command_for_files(pattern, db_url)
104
+ files = Dir.glob("#{backup_directory}/*#{pattern}*")
105
+ case files.size
106
+ when 0
107
+ puts "No backups found for the pattern '#{pattern}'"
108
+ when 1
109
+ command_for_file files.first, db_url
110
+ else
111
+ puts "Too many files match the pattern '#{pattern}': #{files.join("\n ")} "
112
+ puts 'Try a more specific pattern'
113
+ end
114
+ end
115
+
116
+ def command_for_file(file, db_url)
117
+ return puts("No recognized dump file suffix: #{file}") unless (fmt = format_for_file(file)).present?
118
+ "pg_restore -d '#{db_url}' -F #{fmt} -v -c #{file}"
119
+ end
120
+
121
+ namespace :restore do
122
+ desc 'Restores the database from latest backup'
123
+ task :last do
124
+ cmd = nil
125
+ with_config do |_, db_url|
126
+ file = Dir.glob("#{backup_directory}/*").max_by { |f| File.mtime(f) }
127
+ if file
128
+ fmt = format_for_file file
129
+ if fmt.nil?
130
+ puts "No recognized dump file suffix: #{file}"
131
+ else
132
+ cmd = "pg_restore -d '#{db_url}' -F #{fmt} -v #{file}"
133
+ end
134
+ else
135
+ puts 'No backups found'
136
+ end
137
+ end
138
+ unless cmd.nil?
139
+ Rake::Task['db:drop'].invoke
140
+ Rake::Task['db:create'].invoke
141
+ puts cmd
142
+ exec "#{cmd} || exit 0"
143
+ end
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def suffix_for_format(suffix)
150
+ case suffix
151
+ when 'c' then 'dump'
152
+ when 'p' then 'sql'
153
+ when 't' then 'tar'
154
+ when 'd' then 'dir'
155
+ end
156
+ end
157
+
158
+ def format_for_file(file)
159
+ case file
160
+ when /\.dump$/ then 'c'
161
+ when /\.sql$/ then 'p'
162
+ when /\.dir$/ then 'd'
163
+ when /\.tar$/ then 't'
164
+ end
165
+ end
166
+
167
+ def backup_directory(create = false)
168
+ backup_dir = 'db/backups'
169
+ if create && !Dir.exist?(backup_dir)
170
+ puts "Creating #{backup_dir} .."
171
+ FileUtils.mkdir_p(backup_dir)
172
+ end
173
+ backup_dir
174
+ end
175
+
176
+ def with_config
177
+ yield 'ossert', ENV.fetch('DATABASE_URL')
178
+ end
179
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+ namespace :ossert do
3
+ namespace :cache do
4
+ desc 'Reset data cache'
5
+ task :reset do
6
+ ::Ossert.init
7
+ ::Classifier.dataset.delete
8
+ Ossert::Classifiers::Growing.new.train
9
+ true
10
+ end
11
+ end
12
+
13
+ desc 'Collect reference projects'
14
+ task :collect_referencies do
15
+ puts 'Run collecting process'
16
+ time = Benchmark.realtime do
17
+ ::Ossert.init
18
+ ::Project.db.transaction do
19
+ Ossert::Project.cleanup_referencies!
20
+ reference_projects = Ossert::Reference.prepare_projects!
21
+ Ossert::Reference.process_references(reference_projects)
22
+ end
23
+ end
24
+
25
+ puts "Collecting process finished in #{time.round(3)} sec."
26
+ end
27
+
28
+ desc 'Invoke data updates for stale projects'
29
+ task :refresh_data do
30
+ require './config/sidekiq.rb'
31
+ Ossert::Workers::RefreshFetch.perform_async
32
+ end
33
+
34
+ desc 'Add or replace project name exception'
35
+ task :exception, [:name, :github_name] do |_, args|
36
+ raise 'Arguments name and GitHub name expected' unless args.name.present? && args.github_name.present?
37
+ exceptions_repo = ExceptionsRepo.new(Ossert.rom)
38
+ if exceptions_repo[args.name]
39
+ exceptions_repo.update(
40
+ args.name,
41
+ name: args.name,
42
+ github_name: args.github_name
43
+ )
44
+ else
45
+ exceptions_repo.create(
46
+ name: args.name,
47
+ github_name: args.github_name
48
+ )
49
+ end
50
+ puts "Exception '#{args.name}' => '#{args.github_name}' saved!"
51
+ end
52
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ module Workers
4
+ class Fetch
5
+ include Sidekiq::Worker
6
+ include Process
7
+ sidekiq_options unique: :until_executed,
8
+ unique_expiration: 1.hour,
9
+ retry: 3
10
+
11
+ def perform(name, reference = Ossert::Saveable::UNUSED_REFERENCE)
12
+ puts "Fetching data for: '#{name}' (ref: #{reference})"
13
+ pid = fork do
14
+ Ossert.init
15
+ Ossert::Project.fetch_all(name, reference)
16
+ end
17
+ waitpid(pid)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ module Workers
4
+ class FetchBestgemsPage
5
+ include Sidekiq::Worker
6
+ include Process
7
+
8
+ sidekiq_options retry: 3,
9
+ unique: :until_executed
10
+
11
+ def perform(pages, type = :total)
12
+ pages = Array(pages)
13
+ bestgems_page_processor = Kernel.const_get(
14
+ "Ossert::Fetch::Bestgems#{type.to_s.capitalize}Stat"
15
+ )
16
+ pages.each do |page|
17
+ puts "Processing Bestgems page: '#{page}'"
18
+ bestgems_page_processor.process_page(page) do |_, _, gem_name|
19
+ puts "Processing project: '#{gem_name}'"
20
+ pid = fork do
21
+ Ossert.init
22
+ next(puts("Skipping project: '#{gem_name}'")) if Ossert::Project.exist?(gem_name)
23
+ Ossert::Project.fetch_all(gem_name)
24
+ sleep(10)
25
+ end
26
+ waitpid(pid)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ module Workers
4
+ class RefreshFetch
5
+ include Sidekiq::Worker
6
+ include Process
7
+ sidekiq_options unique: :until_executed,
8
+ unique_expiration: 1.hour,
9
+ retry: 3
10
+
11
+ def perform
12
+ pid = fork do
13
+ Ossert.init
14
+ ::Project.select(:name, :reference).where('updated_at < ?', 1.week.ago).paged_each do |project|
15
+ Ossert::Workers::Fetch.perform_async(project.name, project.reference)
16
+ end
17
+ end
18
+ waitpid(pid)
19
+ end
20
+ end
21
+ end
22
+ end
File without changes
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ require 'sidekiq'
3
+ require 'sidekiq-unique-jobs'
4
+ require 'ossert/workers/fetch'
5
+ require 'ossert/workers/fetch_bestgems_page'
6
+ require 'ossert/workers/refresh_fetch'
7
+
8
+ module Ossert
9
+ module Workers
10
+ end
11
+ end
data/lib/ossert.rb ADDED
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ require 'ossert/version'
3
+ require 'sequel'
4
+ require 'active_support/all' # remove later, we use only quarters and index_by here
5
+ require 'json'
6
+ require 'oj'
7
+
8
+ require 'ossert/config'
9
+ Ossert::Config.load(:stats, :classifiers, :translations, :descriptions)
10
+
11
+ require 'ossert/stats'
12
+ require 'ossert/quarters_store'
13
+ require 'ossert/saveable'
14
+ require 'ossert/presenters'
15
+ require 'ossert/project'
16
+ require 'ossert/fetch'
17
+ require 'ossert/reference'
18
+ require 'ossert/classifiers'
19
+ require 'ossert/workers'
20
+
21
+ module Ossert
22
+ def init(database_url = nil)
23
+ Sequel.connect(database_url || ENV.fetch('DATABASE_URL'))
24
+ require 'ossert/repositories'
25
+ end
26
+ module_function :init
27
+
28
+ def description(key)
29
+ descriptions.fetch(key.to_s, "Description for '#{key}' - not found")
30
+ end
31
+ alias descr description
32
+ module_function :descr
33
+
34
+ def descriptions
35
+ @descriptions ||= ::Settings['descriptions']
36
+ end
37
+ module_function :descriptions
38
+
39
+ def translate(key)
40
+ translations.fetch(key.to_s, "Translation for '#{key}' - not found")
41
+ end
42
+ alias t translate
43
+ module_function :t
44
+
45
+ def translations
46
+ @translations ||= ::Settings['translations']
47
+ end
48
+ module_function :translations
49
+
50
+ NO_GITHUB_NAME = '__unknown__'
51
+
52
+ class TooLong
53
+ def self.new
54
+ Ossert::Stats::PER_YEAR_TOO_LONG
55
+ end
56
+ end
57
+
58
+ class Zero
59
+ def self.new
60
+ 0
61
+ end
62
+ end
63
+ end
data/ossert.gemspec ADDED
@@ -0,0 +1,47 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'ossert/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'ossert'
9
+ spec.version = Ossert::VERSION
10
+ spec.authors = ['Sergey Dolganov']
11
+ spec.email = ['sclinede@gmail.com']
12
+
13
+ spec.summary = 'Write a short summary, because Rubygems requires one.'
14
+ spec.description = 'Write a longer description or delete this line.'
15
+ spec.homepage = 'https://github.com/sclinede/ossert'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_runtime_dependency 'octokit', '~> 4.0'
24
+
25
+ spec.add_runtime_dependency 'redis'
26
+ spec.add_runtime_dependency 'redis-namespace'
27
+ spec.add_runtime_dependency 'sidekiq'
28
+ spec.add_runtime_dependency 'sidekiq-unique-jobs'
29
+
30
+ spec.add_runtime_dependency 'activesupport' # TODO: remove dependency later
31
+ spec.add_runtime_dependency 'oj'
32
+ spec.add_runtime_dependency 'multi_json'
33
+ spec.add_runtime_dependency 'sequel'
34
+ spec.add_runtime_dependency 'pg'
35
+ spec.add_runtime_dependency 'nokogiri'
36
+ spec.add_runtime_dependency 'graphr'
37
+ spec.add_runtime_dependency 'rake', '~> 10.0'
38
+
39
+ spec.add_development_dependency 'memory_profiler'
40
+ spec.add_development_dependency 'rubocop'
41
+ spec.add_development_dependency 'bundler', '~> 1.11'
42
+ spec.add_development_dependency 'pry'
43
+ spec.add_development_dependency 'rspec', '~> 3.0'
44
+ spec.add_development_dependency 'simplecov'
45
+ spec.add_development_dependency 'webmock'
46
+ spec.add_development_dependency 'vcr'
47
+ end