• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

benwbrum / fromthepage / 15468382161

05 Jun 2025 01:31PM UTC coverage: 60.706% (-0.02%) from 60.722%
15468382161

push

github

web-flow
Merge pull request #4683 from benwbrum/3854-allow-owners-to-delete-subjects-with-links

3854 - Allow owners to delete subjects with links

1604 of 3236 branches covered (49.57%)

Branch coverage included in aggregate %.

49 of 51 new or added lines in 6 files covered. (96.08%)

4 existing lines in 3 files now uncovered.

7389 of 11578 relevant lines covered (63.82%)

78.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.68
/app/controllers/article_controller.rb
1
class ArticleController < ApplicationController
1✔
2
  include AbstractXmlController
1✔
3

4
  before_action :authorized?, except: [:list, :show, :tooltip, :graph]
1✔
5

6
  def tooltip
1✔
7
    render partial: 'tooltip'
1✔
8
  end
9

10
  def list
1✔
11
    articles = @collection.articles.includes(:categories)
21✔
12
    @categories = @collection.categories
21✔
13
    @vertical_articles = {}
21✔
14
    @categories.each do |category|
21✔
15
      current_articles = articles.where(categories: { id: category.id }).reorder(:title)
53✔
16
      @vertical_articles[category] = sort_vertically(current_articles)
53✔
17
    end
18

19
    @uncategorized_articles = sort_vertically(
21✔
20
      articles.where(categories: { id: nil }).reorder(:title)
21
    )
22
  end
23

24
  def delete
1✔
25
    result = Article::Destroy.new(
2✔
26
      article: @article,
27
      user: current_user,
28
      collection: @collection
29
    ).call
30

31
    if result.success?
2✔
32
      redirect_to collection_subjects_path(@collection.owner, @collection)
2✔
33
    else
×
UNCOV
34
      flash.alert = result.message
×
UNCOV
35
      redirect_to collection_article_show_path(@collection.owner, @collection, @article.id)
×
36
    end
37
  end
38

39
  def edit
1✔
40
    @sex_autocomplete=@collection.articles.distinct.pluck(:sex).select{|e| !e.blank?}
24✔
41
    @race_description_autocomplete=@collection.articles.distinct.pluck(:race_description).select{|e| !e.blank?}
24✔
42
  end
43

44
  def update
1✔
45
    if params[:save]
8✔
46
      result = Article::Update.new(
5✔
47
        article: @article,
48
        article_params: article_params
49
      ).call
50

51
      if result.success?
5✔
52
        record_deed
4✔
53

54
        flash[:notice] = result.notice
4✔
55
        redirect_to collection_article_edit_path(@collection.owner, @collection, @article)
4✔
56
      else
1✔
57
        @article = result.article
1✔
58
        render :edit, status: :unprocessable_entity
1✔
59
      end
3✔
60
    elsif params[:autolink]
3✔
61
      @article.source_text = autolink(@article.source_text)
2✔
62

63
      flash[:notice] = t('.subjects_auto_linking')
2✔
64
      render :edit
2✔
65
    else
66
      # Default to redirect
1✔
67
      redirect_to collection_article_edit_path(@collection.owner, @collection, @article)
1✔
68
    end
69
  end
70

71
  def article_category
1✔
72
    categories = Category.where(id: params[:category_ids])
1✔
73
    @article.categories = categories
1✔
74
    @article.save!
1✔
75

76
    respond_to(&:turbo_stream)
1✔
77
  end
78

79
  def combine_duplicate
1✔
80
    Article::Combine.new(
3✔
81
      article: @article,
82
      from_article_ids: params[:from_article_ids]
83
    ).call
84

85
    flash[:notice] = t('.selected_subjects_combined', title: @article.title)
3✔
86
    redirect_to collection_article_edit_path(@collection.owner, @collection, @article)
3✔
87
  end
88

89
  def graph
1✔
90
    redirect_to :action => :show, :article_id => @article.id
×
91
  end
92

93
  def show
1✔
94
    sql =
95
      'SELECT count(*) as link_count, '+
13✔
96
      'a.title as title, '+
97
      'a.id as article_id '+
98
      'FROM page_article_links to_links '+
99
      'INNER JOIN page_article_links from_links '+
100
      '  ON to_links.page_id = from_links.page_id '+
101
      'INNER JOIN articles a '+
102
      '  ON from_links.article_id = a.id '+
103
      "WHERE to_links.article_id = #{@article.id} "+
104
      " AND from_links.article_id != #{@article.id} "
105
    sql += "GROUP BY a.title, a.id "
13✔
106
    logger.debug(sql)
13✔
107
    article_links = Article.connection.select_all(sql)
13✔
108
    link_total = 0
13✔
109
    link_max = 0
13✔
110
    count_per_rank = { 0 => 0 }
13✔
111
    article_links.each do |l|
13✔
112
      link_count = l['link_count'].to_i
2✔
113
      link_total += link_count
2✔
114
      link_max = [link_count, link_max].max
2✔
115

116
      count_per_rank[link_count] ||= 0
2✔
117
      count_per_rank[link_count] += 1
2✔
118
    end
119

120
    min_rank = 0
13✔
121
    # now we know how many articles each link count has, as well as the size
122
    if params[:min_rank]
13✔
123
      # use the min rank from the params
×
124
      min_rank = params[:min_rank].to_i
×
125
    else
126
      # calculate whether we should reduce the rank
13✔
127
      num_articles = article_links.count
13✔
128
      while num_articles > DEFAULT_ARTICLES_PER_GRAPH && min_rank < link_max
13✔
129
        # remove the outer rank
×
130
        num_articles -= count_per_rank[min_rank] || 0 # hash is sparse
×
131
        min_rank += 1
×
132
        logger.debug("DEBUG: \tnum articles now #{num_articles}\n")
×
133
      end
134
    end
135

136
    dot_source =
137
      render_to_string(:partial => "graph.dot",
13✔
138
                       :layout => false,
139
                       :locals => { :article_links => article_links,
140
                                    :link_total => link_total,
141
                                    :link_max => link_max,
142
                                    :min_rank => min_rank })
143

144
    dot_file = "#{Rails.root}/public/images/working/dot/#{@article.id}.dot"
13✔
145
    File.open(dot_file, "w") do |f|
13✔
146
      f.write(dot_source)
13✔
147
    end
148
    dot_out = "#{Rails.root}/public/images/working/dot/#{@article.id}.png"
13✔
149
    dot_out_map = "#{Rails.root}/public/images/working/dot/#{@article.id}.map"
13✔
150

151
    system "#{Rails.application.config.neato} -Tcmapx -o#{dot_out_map} -Tpng #{dot_file} -o #{dot_out}"
13✔
152

153
    @map = File.read(dot_out_map)
13✔
154
    @article.graph_image = dot_out
13✔
155
    @article.save!
13✔
156
    session[:col_id] = @collection.slug
13✔
157
  end
158

159
  # display the article upload form
160
  def upload_form
1✔
161
  end
162

163
  # actually process the uploaded CSV
164
  def subject_upload
1✔
165
    @collection = Collection.find params[:upload][:collection_id]
×
166
    # read the file
167
    file = params[:upload][:file].tempfile
×
168

169
    # csv = CSV.read(params[:upload][:file].tempfile, :headers => true)
170
    begin
171
      csv = CSV.read(params[:upload][:file].tempfile, :headers=>true)
×
172
    rescue
173
      contents = File.read(params[:upload][:file].tempfile)
×
174
      detection = CharlockHolmes::EncodingDetector.detect(contents)
×
175

176
      csv = CSV.read(params[:upload][:file].tempfile,
×
177
                      :encoding => "bom|#{detection[:encoding]}",
178
                      :liberal_parsing => true,
179
                      :headers => true)
180
    end
181

182
    provenance = params[:upload][:file].original_filename + " (uploaded #{Time.now} UTC)"
×
183

184
    # check the values
185
    if csv.headers.include?('HEADING') && csv.headers.include?('URI') && csv.headers.include?('ARTICLE') && csv.headers.include?('CATEGORY')
×
186
      # create subjects if heading checks out
×
187
      csv.each do |row|
×
188
        title = row['HEADING']
×
189
        article = @collection.articles.where(:title => title).first || Article.new(:title => title, :provenance => provenance)
×
190
        article.collection = @collection
×
191
        article.source_text = row['ARTICLE']
×
192
        article.uri = row['URI']
×
193
        article.categories << find_or_create_category(@collection, row['CATEGORY'])
×
194
        article.save!
×
195
      end
196
      # redirect to subject list
197
      redirect_to collection_subjects_path(@collection.owner, @collection)
×
198
    else
199
      # flash message and redirect to upload form on problems
×
200
      flash[:error] = t('.csv_file_must_contain_headers')
×
201
      redirect_to article_upload_form_path(@collection)
×
202
    end
203
  end
204

205
  def upload_example
1✔
206
    example = File.read(File.join(Rails.root, 'app', 'views', 'static', 'subject_example.csv'))
×
207
    send_data example, filename: "subject_example.csv"
×
208
  end
209

210
  protected
1✔
211

212
  def record_deed
1✔
213
    deed = Deed.new
4✔
214
    deed.article = @article
4✔
215
    deed.deed_type = DeedType::ARTICLE_EDIT
4✔
216
    deed.collection = @article.collection
4✔
217
    deed.user = current_user
4✔
218
    deed.save!
4✔
219
    update_search_attempt_contributions
4✔
220
  end
221

222
  private
1✔
223

224
  def authorized?
1✔
225
    redirect_to dashboard_path unless user_signed_in?
30✔
226
  end
227

228
  def article_params
1✔
229
    params.require(:article).permit(:title, :uri, :short_summary, :source_text, :latitude, :longitude, :birth_date, :death_date, :race_description, :sex, :bibliography, :begun, :ended, category_ids: [])
5✔
230
  end
231

232
  def sort_vertically(articles)
1✔
233
    return [] unless articles.any?
74✔
234

235
    rows = (articles.length.to_f / LIST_NUM_COLUMNS).ceil
52✔
236
    vertical_articles = Array.new(rows) { Array.new(LIST_NUM_COLUMNS) }
105✔
237

238
    articles.each_with_index do |article, index|
52✔
239
      row = index % rows
87✔
240
      col = index / rows
87✔
241
      vertical_articles[row][col] = article
87✔
242
    end
243

244
    vertical_articles
52✔
245
  end
246

247
  def find_or_create_category(collection, title)
1✔
248
    category = collection.categories.where(title: title).first
×
249
    if category.nil?
×
250
      category = Category.new(title: title)
×
251
      collection.categories << category
×
252
    end
253

254
    category
×
255
  end
256
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc