diff options
author | Rhenium <rhenium@rhe.jp> | 2014-03-18 23:07:02 +0900 |
---|---|---|
committer | Rhenium <rhenium@rhe.jp> | 2014-03-18 23:07:02 +0900 |
commit | adef4c0c2087e1e475ab1aa4b4df68554bdb1a88 (patch) | |
tree | 38b10ca182f2676e3ed55ba1ea778bcf941b5fd7 /app/models/tweet.rb | |
parent | c73aecc3605aee69a12b4f02a4d01e00b77cac83 (diff) | |
download | aclog-adef4c0c2087e1e475ab1aa4b4df68554bdb1a88.tar.gz |
refactor models
Diffstat (limited to 'app/models/tweet.rb')
-rw-r--r-- | app/models/tweet.rb | 235 |
1 files changed, 114 insertions, 121 deletions
diff --git a/app/models/tweet.rb b/app/models/tweet.rb index b7121a9..9229e10 100644 --- a/app/models/tweet.rb +++ b/app/models/tweet.rb @@ -10,6 +10,8 @@ class Tweet < ActiveRecord::Base has_many :favoriters, -> { order("favorites.id") }, through: :favorites, source: :user has_many :retweeters, -> { order("retweets.id") }, through: :retweets, source: :user + scope :eager_load_for_html, -> { eager_load(:user) } + scope :recent, ->(period = 3.days) { where("tweets.id > ?", snowflake_min(Time.zone.now - period)) } scope :reacted, ->(count = nil) { where("reactions_count >= ?", (count || 1).to_i) } scope :not_protected, -> { joins(:user).references(:user).where(users: { protected: false }) } @@ -35,6 +37,117 @@ class Tweet < ActiveRecord::Base joins("INNER JOIN ((#{un})) reactions ON reactions.tweet_id = tweets.id") } + class << self + def create_from_json(json) + tweet = transaction do + self.find_by(id: json[:id]) || + self.create!(id: json[:id], + text: extract_entities(json), + source: json[:source], + tweeted_at: json[:created_at], + in_reply_to_id: json[:in_reply_to_status_id], + user: User.create_from_json(json[:user])) + end + rescue ActiveRecord::RecordNotUnique => e + logger.debug("Duplicate tweet: #{tweet}: #{e.class}") # collector may be threaded + self.find(json[:id]) + end + + def create_from_twitter_object(obj) + t = self.create_from_json(obj.attrs) + t.update_reactions_count(json: obj.attrs) + t + end + + def destroy_from_json(json) + deleted_count = self.delete(json[:delete][:status][:id]) + + if deleted_count > 0 + Favorite.where(tweet_id: json[:delete][:status][:id]).delete_all + Retweet.where(tweet_id: json[:delete][:status][:id]).delete_all + true + else + false + end + end + + def import(id, client = nil) + client ||= Account.random.client + + st = client.status(id) + tweet = self.create_from_twitter_object(st) + tweet.update(text: extract_entities(st.attrs), + source: st.attrs[:source], + in_reply_to_id: (tweet.in_reply_to_id || st.attrs[:in_reply_to_status_id])) + + nt = tweet + nt = self.create_from_twitter_object(client.status(nt.in_reply_to_id)) while !nt.in_reply_to && nt.in_reply_to_id + + tweet.reload + end + + def filter_by_query(query) + strings = [] + query.gsub!(/"((?:\\"|[^"])*?)"/) {|m| strings << $1; "##{strings.size - 1}" } + + escape_text = -> str do + str.gsub(/#(\d+)/) { strings[$1.to_i] } + .gsub("%", "\\%") + .gsub("*", "%") + .gsub("_", "\\_") + .gsub("?", "_") + end + + parse_condition = ->(scoped, token) do + positive = !token.slice!(/^[-!]/) + + where_args = case token + when /^(?:user|from):([A-Za-z0-9_]{1,20})$/ + u = User.find_by(screen_name: $1) + uid = u && u.id || -1 + { user_id: uid } + when /^fav(?:orite)?s?:(\d+)$/ + ["favorites_count >= ?", $1.to_i] + when /^(?:retweet|rt)s?:(\d+)$/ + ["retweets_count >= ?", $1.to_i] + when /^(?:sum|(?:re)?act(?:ion)?s?):(\d+)$/ + ["reactions_count >= ?", $1.to_i] + when /^(?:source|via):(.+)$/ + ["source LIKE ?", escape_text.call($1)] + when /^text:(.+)$/ + ["text LIKE ?", "%" + escape_text.call($1) + "%"] + else + nil + end + + positive ? scoped.where(where_args) : scoped.where.not(where_args) + end + + query.scan(/\S+/).inject(self.all) {|s, token| parse_condition.call(s, token) } + end + + private + # replace t.co with expanded_url + def extract_entities(json) + entity_values = json[:entities].values.flatten.sort_by {|v| v[:indices].first } + entity_values.select! {|e| e[:url] } + + result = "" + last_index = entity_values.inject(0) do |last_index, entity| + result << json[:text][last_index...entity[:indices].first] + result << entity[:expanded_url] + entity[:indices].last + end + result << json[:text][last_index..-1] + + result + end + + def snowflake_min(time) + (time.to_datetime.to_i * 1000 - 1288834974657) << 22 + end + end + def twitter_url "https://twitter.com/#{self.user.screen_name}/status/#{self.id}" end @@ -45,7 +158,7 @@ class Tweet < ActiveRecord::Base level = 0 while node.in_reply_to && level < max_level - nodes.unshift(node = node.in_reply_to) + nodes << (node = node.in_reply_to) level += 1 end nodes @@ -71,124 +184,4 @@ class Tweet < ActiveRecord::Base "retweets_count = GREATEST(retweets_count #{rts_op} #{retweets_count.abs}, #{(json[:retweet_count] || 0).to_i}), " + "reactions_count = favorites_count + retweets_count") end - - def self.create_from_json(json) - tweet = transaction do - self.find_by(id: json[:id]) || - self.create!(id: json[:id], - text: extract_entities(json), - source: json[:source], - tweeted_at: json[:created_at], - in_reply_to_id: json[:in_reply_to_status_id], - user: User.create_from_json(json[:user])) - end - rescue ActiveRecord::RecordNotUnique => e - logger.debug("Duplicate tweet: #{tweet}: #{e.class}") - rescue => e - logger.error("Failed to create a tweet: #{tweet}: #{e.class}: #{e.message}/#{e.backtrace.join("\n")}") - ensure - return tweet - end - - def self.create_from_twitter_object(obj) - t = self.create_from_json(obj.attrs) - t.update_reactions_count(json: obj.attrs) - t - end - - def self.destroy_from_json(json) - deleted_count = self.delete(json[:delete][:status][:id]) - - if deleted_count > 0 - Favorite.where(tweet_id: json[:delete][:status][:id]).delete_all - Retweet.where(tweet_id: json[:delete][:status][:id]).delete_all - true - else - false - end - end - - def self.import(id, client = nil) - client ||= Account.random.client - - st = client.status(id) - tweet = self.create_from_twitter_object(st) - tweet.update(text: extract_entities(st.attrs), - source: st.attrs[:source], - in_reply_to_id: st.attrs[:in_reply_to_status_id]) - - begin - nt = tweet - nt = self.create_from_twitter_object(client.status(nt.in_reply_to_id)) while !nt.in_reply_to && nt.in_reply_to_id - rescue Twitter::Error - logger.warn($!) - end - - tweet.reload - end - - def self.eager_load_for_html - self.eager_load(:user) - end - - def self.filter_by_query(query) - strings = [] - query.gsub!(/"((?:\\"|[^"])*?)"/) {|m| strings << $1; "##{strings.size - 1}" } - - escape_text = -> str do - str.gsub(/#(\d+)/) { strings[$1.to_i] } - .gsub("%", "\\%") - .gsub("*", "%") - .gsub("_", "\\_") - .gsub("?", "_") - end - - parse_condition = ->(scoped, token) do - positive = !token.slice!(/^[-!]/) - - where_args = case token - when /^(?:user|from):([A-Za-z0-9_]{1,20})$/ - u = User.find_by(screen_name: $1) - uid = u && u.id || -1 - { user_id: uid } - when /^fav(?:orite)?s?:(\d+)$/ - ["favorites_count >= ?", $1.to_i] - when /^(?:retweet|rt)s?:(\d+)$/ - ["retweets_count >= ?", $1.to_i] - when /^(?:sum|(?:re)?act(?:ion)?s?):(\d+)$/ - ["reactions_count >= ?", $1.to_i] - when /^(?:source|via):(.+)$/ - ["source LIKE ?", escape_text.call($1)] - when /^text:(.+)$/ - ["text LIKE ?", "%" + escape_text.call($1) + "%"] - else - nil - end - - positive ? scoped.where(where_args) : scoped.where.not(where_args) - end - - query.scan(/\S+/).inject(self.all) {|s, token| parse_condition.call(s, token) } - end - - private - # replace t.co with expanded_url - def self.extract_entities(json) - entity_values = json[:entities].values.flatten.sort_by {|v| v[:indices].first } - entity_values.select! {|e| e[:url] } - - result = "" - last_index = entity_values.inject(0) do |last_index, entity| - result << json[:text][last_index...entity[:indices].first] - result << entity[:expanded_url] - entity[:indices].last - end - result << json[:text][last_index..-1] - - result - end - - def self.snowflake_min(time) - (time.to_datetime.to_i * 1000 - 1288834974657) << 22 - end end |