<commit_before>require 'nokogiri'

##
# Feed entry model. Each instance of this class represents an entry in an RSS or Atom feed.
#
# Instances of this class are saved in the database when fetching and parsing feeds. It's not intended to be
# instanced by the user.
#
# Each entry belongs to exactly one feed.
#
# Each entry has many entry-states, exactly one for each user subscribed to the feed. Each entry-state indicates
# whether each user has read or not this entry.
#
# When a new entry is saved in the database for the first time, it is marked as unread for all users subscribed to
# its feed (by saving as many entry_state instances as subscribed users into the database, all of them with the attribute
# "read" set to false).
#
# Each entry is uniquely identified by its guid. Duplicate guids are not allowed.
#
# Attributes of the model:
# - title
# - url
# - author
# - content
# - summary
# - published
# - guid
#
# Title, url and guid are mandatory. Urls are validated with this regex:
#   /\Ahttps?:\/\/.+\..+\z/
#
# All fields except "published" are sanitized before validation; this is, before saving/updating each
# instance in the database.

class Entry < ActiveRecord::Base
  include ActionView::Helpers::SanitizeHelper

  attr_accessible #none

  belongs_to :feed
  validates :feed_id, presence: true

  has_many :entry_states, dependent: :destroy, uniq: true

  validates :title, presence: true
  validates :url, presence: true, format: {with: /\Ahttps?:\/\/.+\..+\z/}
  validates :guid, presence: true, uniqueness: {case_sensitive: false}

  before_validation :sanitize_attributes
  before_save :links_new_tab
  after_create :set_unread_state

  ##
  # Return a boolean that indicates whether this entry has been marked as read by the passed user.
  #
  # Receives as argument the user for which the read/unread state will be retrieved.

  def read_by?(user)
    state = EntryState.where(entry_id: self.id, user_id: user.id).first
    return state.read
  end

  private

  ##
  # Sanitize the title, url, author, content, summary and guid of the entry.
  #
  # Despite this sanitization happening before saving in the database, sanitize helpers must still be used in the views.
  # Better paranoid than sorry!
  #


  def sanitize_attributes
    default_attribute_values
    self.title = sanitize self.title
    self.url = sanitize self.url
    self.author = sanitize self.author
    self.content = sanitize self.content
    self.summary = sanitize self.summary
    self.guid = sanitize self.guid
  end

  ##
  # Give default values to the title and guid attributes if they are empty.
  # Their default value is the value of the "url" attribute.
  #
  # This way, the only actually mandatory attribute when saving an entry is the url, the rest of the fields
  # are given value before saving in the database (but they are mandatory in the database nonetheless).

  def default_attribute_values
    self.guid = self.url if self.guid.blank?
    self.title = self.url if self.title.blank?
  end

  ##
  # For each user subscribed to this entry's feed, save an entry_state instance with the "read" attribute set to false.
  #
  # Or in layman's terms: mark this entry as unread for all users subscribed to the feed.

  def set_unread_state
    self.feed.users.each do |user|
      entry_state = user.entry_states.build read: false
      entry_state.entry_id = self.id
      entry_state.save!
    end
  end

  ##
  # Ensure that any links in the summary and content open in a new tab, by adding the target="_blank"
  # attribute if necessary

  def links_new_tab
    self.summary = add_target_blank self.summary
    self.content = add_target_blank self.content
  end

  ##
  # Add the target="_blank" attribute to any links in the passed HTML fragment.
  # Receives as argument a string with an HTML fragment.
  # The attribute will overwrite any target="" attribute that was present in the links

  def add_target_blank(html_fragment)
    htmlDoc = Nokogiri::HTML html_fragment
    htmlDoc.css('a').each do |link|
      link['target'] = '_blank'
    end
    return htmlDoc.css('body').children.to_s
  end

end
<commit_msg>When adding an entry to a feed's entries, before marking it as unread for each subscribed user, ensure that the user does not already have an entry_state (read or unread) for that entry.<commit_after>require 'nokogiri'

##
# Feed entry model. Each instance of this class represents an entry in an RSS or Atom feed.
#
# Instances of this class are saved in the database when fetching and parsing feeds. It's not intended to be
# instanced by the user.
#
# Each entry belongs to exactly one feed.
#
# Each entry has many entry-states, exactly one for each user subscribed to the feed. Each entry-state indicates
# whether each user has read or not this entry.
#
# When a new entry is saved in the database for the first time, it is marked as unread for all users subscribed to
# its feed (by saving as many entry_state instances as subscribed users into the database, all of them with the attribute
# "read" set to false).
#
# Each entry is uniquely identified by its guid. Duplicate guids are not allowed.
#
# Attributes of the model:
# - title
# - url
# - author
# - content
# - summary
# - published
# - guid
#
# Title, url and guid are mandatory. Urls are validated with this regex:
#   /\Ahttps?:\/\/.+\..+\z/
#
# All fields except "published" are sanitized before validation; this is, before saving/updating each
# instance in the database.

class Entry < ActiveRecord::Base
  include ActionView::Helpers::SanitizeHelper

  attr_accessible #none

  belongs_to :feed
  validates :feed_id, presence: true

  has_many :entry_states, dependent: :destroy, uniq: true

  validates :title, presence: true
  validates :url, presence: true, format: {with: /\Ahttps?:\/\/.+\..+\z/}
  validates :guid, presence: true, uniqueness: {case_sensitive: false}

  before_validation :sanitize_attributes
  before_save :links_new_tab
  after_create :set_unread_state

  ##
  # Return a boolean that indicates whether this entry has been marked as read by the passed user.
  #
  # Receives as argument the user for which the read/unread state will be retrieved.

  def read_by?(user)
    state = EntryState.where(entry_id: self.id, user_id: user.id).first
    return state.read
  end

  private

  ##
  # Sanitize the title, url, author, content, summary and guid of the entry.
  #
  # Despite this sanitization happening before saving in the database, sanitize helpers must still be used in the views.
  # Better paranoid than sorry!
  #


  def sanitize_attributes
    default_attribute_values
    self.title = sanitize self.title
    self.url = sanitize self.url
    self.author = sanitize self.author
    self.content = sanitize self.content
    self.summary = sanitize self.summary
    self.guid = sanitize self.guid
  end

  ##
  # Give default values to the title and guid attributes if they are empty.
  # Their default value is the value of the "url" attribute.
  #
  # This way, the only actually mandatory attribute when saving an entry is the url, the rest of the fields
  # are given value before saving in the database (but they are mandatory in the database nonetheless).

  def default_attribute_values
    self.guid = self.url if self.guid.blank?
    self.title = self.url if self.title.blank?
  end

  ##
  # For each user subscribed to this entry's feed, save an entry_state instance with the "read" attribute set to false.
  #
  # Or in layman's terms: mark this entry as unread for all users subscribed to the feed.

  def set_unread_state
    self.feed.users.each do |user|
      if user.entry_states.where(:entry_id == self.id).blank?
        entry_state = user.entry_states.build read: false
        entry_state.entry_id = self.id
        entry_state.save!
      end
    end
  end

  ##
  # Ensure that any links in the summary and content open in a new tab, by adding the target="_blank"
  # attribute if necessary

  def links_new_tab
    self.summary = add_target_blank self.summary
    self.content = add_target_blank self.content
  end

  ##
  # Add the target="_blank" attribute to any links in the passed HTML fragment.
  # Receives as argument a string with an HTML fragment.
  # The attribute will overwrite any target="" attribute that was present in the links

  def add_target_blank(html_fragment)
    htmlDoc = Nokogiri::HTML html_fragment
    htmlDoc.css('a').each do |link|
      link['target'] = '_blank'
    end
    return htmlDoc.css('body').children.to_s
  end

end

{'fraction_non_alphanumeric': 0.07453416149068323, 'fraction_numerical': 0.0, 'mean_word_length': 3.489216201998948, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1733482', 'n_tokens_mistral': 2499, 'n_tokens_neox': 2430, 'n_words': 1062}