hivemind-ruby 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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +219 -0
- data/Rakefile +20 -0
- data/bin/hivemind-ruby +31 -0
- data/hivemind-ruby.gemspec +49 -0
- data/lib/hive.rb +21 -0
- data/lib/hive/models/account.rb +138 -0
- data/lib/hive/models/base.rb +90 -0
- data/lib/hive/models/block.rb +88 -0
- data/lib/hive/models/community.rb +13 -0
- data/lib/hive/models/feed_cache.rb +14 -0
- data/lib/hive/models/flag.rb +11 -0
- data/lib/hive/models/follow.rb +26 -0
- data/lib/hive/models/member.rb +18 -0
- data/lib/hive/models/modlog.rb +7 -0
- data/lib/hive/models/payment.rb +39 -0
- data/lib/hive/models/post.rb +422 -0
- data/lib/hive/models/post_tag.rb +19 -0
- data/lib/hive/models/posts_cache.rb +110 -0
- data/lib/hive/models/reblog.rb +11 -0
- data/lib/hive/models/state.rb +41 -0
- data/lib/hive/version.rb +4 -0
- metadata +232 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'safe_attributes'
|
3
|
+
require 'composite_primary_keys'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'marginalia'
|
7
|
+
rescue LoadError
|
8
|
+
# skip
|
9
|
+
end
|
10
|
+
|
11
|
+
module Hive
|
12
|
+
|
13
|
+
# Abstract calss for all {Hive} active record classes.
|
14
|
+
class Base < ActiveRecord::Base
|
15
|
+
include SafeAttributes
|
16
|
+
|
17
|
+
self.abstract_class = true
|
18
|
+
|
19
|
+
database_url = ENV.fetch('DATABASE_URL', 'postgresql://user:pass@localhost:5432/hive')
|
20
|
+
database_uri = URI.parse(database_url)
|
21
|
+
database = database_uri.path.split('/').last
|
22
|
+
|
23
|
+
establish_connection({
|
24
|
+
adapter: database_uri.scheme,
|
25
|
+
host: ENV.fetch('HIVE_HOST', database_uri.host),
|
26
|
+
port: ENV.fetch('HIVE_PORT', database_uri.port),
|
27
|
+
username: ENV.fetch('HIVE_USERNAME', database_uri.user),
|
28
|
+
password: ENV.fetch('HIVE_PASSWORD', database_uri.password),
|
29
|
+
database: ENV.fetch('HIVE_DATABASE', database),
|
30
|
+
timeout: 60
|
31
|
+
})
|
32
|
+
|
33
|
+
scope :invertable, lambda { |expression, args, options = {invert: false}|
|
34
|
+
args = [args].flatten
|
35
|
+
|
36
|
+
if !!options[:invert]
|
37
|
+
where.not(expression, *args)
|
38
|
+
else
|
39
|
+
where(expression, *args)
|
40
|
+
end
|
41
|
+
}
|
42
|
+
|
43
|
+
# Please note, these are not comprehensive safeguards for marking records
|
44
|
+
# read-only. It's better to completely revoke write access from the user
|
45
|
+
# role at the lowest DBMS permission level.
|
46
|
+
|
47
|
+
after_initialize :readonly!
|
48
|
+
|
49
|
+
# Disabled
|
50
|
+
def self.delete(_=nil); raise ActiveRecord::ReadOnlyRecord; end
|
51
|
+
|
52
|
+
# Disabled
|
53
|
+
def self.delete_all(_=nil); raise ActiveRecord::ReadOnlyRecord; end
|
54
|
+
|
55
|
+
# Disabled
|
56
|
+
def self.update_all(_=nil); raise ActiveRecord::ReadOnlyRecord; end
|
57
|
+
|
58
|
+
# Disabled
|
59
|
+
def delete(_=nil); raise ActiveRecord::ReadOnlyRecord; end
|
60
|
+
|
61
|
+
# Disabled
|
62
|
+
def update(*_); raise ActiveRecord::ReadOnlyRecord; end
|
63
|
+
|
64
|
+
# Uses ducktyping to query on account so that you can pass any type and
|
65
|
+
# still get a valid match.
|
66
|
+
#
|
67
|
+
# So you can do:
|
68
|
+
#
|
69
|
+
# Hive::Post.where(author: 'alice')
|
70
|
+
# Hive::Post.where(author: %w(alice bob))
|
71
|
+
# Hive::Post.where(author: Hive::Account.find_by_name('alice'))
|
72
|
+
# Hive::Post.where(author: Hive::Account.where('name LIKE ?', '%z%'))
|
73
|
+
#
|
74
|
+
# @param account {String|<String>|Account|ActiveRecord::Relation} the
|
75
|
+
# account(s) to match on
|
76
|
+
def self.transform_account_selector(account)
|
77
|
+
case account
|
78
|
+
when String then account
|
79
|
+
when Array then account
|
80
|
+
when Account then account.name
|
81
|
+
else
|
82
|
+
unless account.is_a? ActiveRecord::Relation
|
83
|
+
raise "Don't know what to do with: #{account.class}"
|
84
|
+
end
|
85
|
+
|
86
|
+
account.select(:name)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Hive
|
2
|
+
|
3
|
+
# Tracks blocks.
|
4
|
+
#
|
5
|
+
# To find blocks that have promoted posts:
|
6
|
+
#
|
7
|
+
# blocks = Hive::Block.with_promoted_posts
|
8
|
+
#
|
9
|
+
# With this result, we can get a list of post promoters in that block:
|
10
|
+
#
|
11
|
+
# accounts = blocks.first.post_promoters
|
12
|
+
#
|
13
|
+
# To find blocks that have normal intervals:
|
14
|
+
#
|
15
|
+
# blocks = Hive::Block.block_interval(is: 3.seconds)
|
16
|
+
#
|
17
|
+
# To find blocks that have longer than normal intervals:
|
18
|
+
#
|
19
|
+
# blocks = Hive::Block.block_interval(min: 4.seconds)
|
20
|
+
#
|
21
|
+
# To find blocks that have shorter than normal intervals:
|
22
|
+
#
|
23
|
+
# blocks = Hive::Block.block_interval(max: 2.seconds)
|
24
|
+
#
|
25
|
+
# To find blocks that have intervals in a certain range:
|
26
|
+
#
|
27
|
+
# blocks = Hive::Block.block_interval(min: 15.seconds, max: 30.seconds)
|
28
|
+
#
|
29
|
+
# Full report of slow blocks on the entire chain, grouped by date, sorted by
|
30
|
+
# the total slow blocks for that day:
|
31
|
+
#
|
32
|
+
# puts JSON.pretty_generate Hive::Block.block_interval(min: 4.seconds).
|
33
|
+
# group('CAST(hive_blocks.created_at AS DATE)').order("count_all").count
|
34
|
+
#
|
35
|
+
# Same report as above, grouped by month, sorted by the total slow blocks for
|
36
|
+
# that month:
|
37
|
+
#
|
38
|
+
# puts JSON.pretty_generate Hive::Block.block_interval(min: 4.seconds).
|
39
|
+
# group("TO_CHAR(hive_blocks.created_at, 'YYYY-MM')").order("count_all").count
|
40
|
+
#
|
41
|
+
# Same report as above, sorted by month:
|
42
|
+
#
|
43
|
+
# puts JSON.pretty_generate Hive::Block.block_interval(min: 4.seconds).
|
44
|
+
# group("TO_CHAR(hive_blocks.created_at, 'YYYY-MM')").
|
45
|
+
# order("to_char_hive_blocks_created_at_yyyy_mm").count
|
46
|
+
#
|
47
|
+
class Block < Base
|
48
|
+
self.table_name = :hive_blocks
|
49
|
+
self.primary_key = :num
|
50
|
+
|
51
|
+
belongs_to :previous, primary_key: :hash, foreign_key: :prev, class_name: 'Block'
|
52
|
+
belongs_to :next, primary_key: :prev, foreign_key: :hash, class_name: 'Block'
|
53
|
+
has_many :payments, foreign_key: :block_num
|
54
|
+
has_many :promoted_posts, through: :payments, source: :post
|
55
|
+
has_many :post_promoters, through: :payments, source: :from
|
56
|
+
|
57
|
+
scope :with_promoted_posts, -> { where num: Payment.select(:block_num) }
|
58
|
+
scope :decorate_block_interval, lambda { |units = :seconds|
|
59
|
+
columns = [arel_table[Arel.star]]
|
60
|
+
columns << "#{block_interval_column(units)} AS block_interval"
|
61
|
+
|
62
|
+
joins(:previous).select(columns)
|
63
|
+
}
|
64
|
+
scope :block_interval, lambda { |options = {is: 3.seconds}|
|
65
|
+
is = options[:is]
|
66
|
+
min = options[:min] || is
|
67
|
+
max = options[:max] || is
|
68
|
+
r = joins(:previous)
|
69
|
+
|
70
|
+
if !!min && !!max
|
71
|
+
if min == max
|
72
|
+
r = r.where("#{block_interval_column} = ?", min)
|
73
|
+
else
|
74
|
+
r = r.where("#{block_interval_column} BETWEEN ? AND ?", min, max)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
r = r.where("#{block_interval_column} <= ?", max) if !!max
|
78
|
+
r = r.where("#{block_interval_column} >= ?", min) if !!min
|
79
|
+
end
|
80
|
+
|
81
|
+
r
|
82
|
+
}
|
83
|
+
private
|
84
|
+
def self.block_interval_column(units = :seconds)
|
85
|
+
"EXTRACT(#{units} FROM hive_blocks.created_at - previous_hive_blocks.created_at)"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Hive
|
2
|
+
|
3
|
+
# Tracks communities.
|
4
|
+
class Community < Base
|
5
|
+
self.table_name = :hive_communities
|
6
|
+
self.primary_key = :name
|
7
|
+
|
8
|
+
has_many :posts, primary_key: :name, foreign_key: :community, inverse_of: :community_record
|
9
|
+
has_many :post_tags, through: :posts
|
10
|
+
has_many :members, primary_key: :name, foreign_key: :community
|
11
|
+
has_many :accounts, through: :members
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Hive
|
2
|
+
|
3
|
+
# Tracks feed cache.
|
4
|
+
class FeedCache < Base
|
5
|
+
self.table_name = :hive_feed_cache
|
6
|
+
self.primary_keys = %i(post_id account_id)
|
7
|
+
|
8
|
+
belongs_to :post
|
9
|
+
belongs_to :account
|
10
|
+
|
11
|
+
scope :posts, lambda { |post| where(post: post) }
|
12
|
+
scope :accounts, lambda { |account| where(account: account) }
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Hive
|
2
|
+
|
3
|
+
# Tracks post flags.
|
4
|
+
class Flag < Base
|
5
|
+
self.table_name = :hive_flags
|
6
|
+
self.primary_keys = %i(account post_id)
|
7
|
+
|
8
|
+
belongs_to :post
|
9
|
+
belongs_to :account_record, primary_key: :name, foreign_key: :account, class_name: 'Account'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Hive
|
2
|
+
|
3
|
+
# Tracks follows, mutes, and follow/mute resets.
|
4
|
+
class Follow < Base
|
5
|
+
self.table_name = :hive_follows
|
6
|
+
self.primary_keys = %i(follower following)
|
7
|
+
|
8
|
+
belongs_to :follower_account, foreign_key: :follower, class_name: 'Account'
|
9
|
+
belongs_to :following_account, foreign_key: :following, class_name: 'Account'
|
10
|
+
|
11
|
+
scope :state, lambda { |state_name, options = {invert: false}|
|
12
|
+
state = case state_name
|
13
|
+
when :reset then 0
|
14
|
+
when :follow then 1
|
15
|
+
when :mute then 2
|
16
|
+
else; -1
|
17
|
+
end
|
18
|
+
|
19
|
+
if !!options[:invert]
|
20
|
+
where.not(state: state)
|
21
|
+
else
|
22
|
+
where(state: state)
|
23
|
+
end
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Hive
|
2
|
+
|
3
|
+
# Tracks community members.
|
4
|
+
class Member < Base
|
5
|
+
self.table_name = :hive_members
|
6
|
+
self.primary_keys = %i(community account)
|
7
|
+
|
8
|
+
has_many :communities, primary_key: :community, foreign_key: :name
|
9
|
+
has_many :accounts, primary_key: :account, foreign_key: :name
|
10
|
+
has_many :posts, through: :communities
|
11
|
+
has_many :post_tags, through: :posts
|
12
|
+
|
13
|
+
scope :admin, lambda { |admin = true| where(is_admin: admin) }
|
14
|
+
scope :mod, lambda { |mod = true| where(is_mod: mod) }
|
15
|
+
scope :approved, lambda { |approved = true| where(is_approved: approved) }
|
16
|
+
scope :muted, lambda { |muted = true| where(is_muted: muted) }
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Hive
|
2
|
+
|
3
|
+
# Tracks the payments sent to the `null` account for Post Promotion purposes.
|
4
|
+
#
|
5
|
+
# To grab the top 100 most promoted, grouped by post promoted sum:
|
6
|
+
#
|
7
|
+
# Hive::Payment.top_amount(:post, 100)
|
8
|
+
# Hive::Payment.top_amount # same as #top_amount
|
9
|
+
class Payment < Base
|
10
|
+
self.table_name = :hive_payments
|
11
|
+
|
12
|
+
belongs_to :block, foreign_key: :block_num
|
13
|
+
belongs_to :post
|
14
|
+
belongs_to :from, foreign_key: :from_account, class_name: 'Account'
|
15
|
+
belongs_to :to, foreign_key: :to_account, class_name: 'Account'
|
16
|
+
|
17
|
+
scope :to_null, -> { where(to_account: Account.where(name: 'null')) }
|
18
|
+
|
19
|
+
scope :token, lambda { |token = 'SBD', options = {invert: false}|
|
20
|
+
if !!options[:invert]
|
21
|
+
where.not(token: token)
|
22
|
+
else
|
23
|
+
where(token: token)
|
24
|
+
end
|
25
|
+
}
|
26
|
+
|
27
|
+
scope :top_amount, lambda { |what = :post, limit = 100|
|
28
|
+
group(what).limit(limit).order('sum_amount desc').sum(:amount)
|
29
|
+
}
|
30
|
+
|
31
|
+
scope :post, lambda { |post, options = {invert: false}|
|
32
|
+
if !!options[:invert]
|
33
|
+
where.not(post: post)
|
34
|
+
else
|
35
|
+
where(post: post)
|
36
|
+
end
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,422 @@
|
|
1
|
+
module Hive
|
2
|
+
|
3
|
+
# Tracks all content including posts, comments (replies).
|
4
|
+
#
|
5
|
+
# To find a post, you can do a lookup using the {Hive::Post#find_by_slug} scope:
|
6
|
+
#
|
7
|
+
# post = Hive::Post.find_by_slug '@steemit/firstpost'
|
8
|
+
#
|
9
|
+
# A post has many children, which are direct replies to that post.
|
10
|
+
#
|
11
|
+
# children = post.children
|
12
|
+
#
|
13
|
+
# Each child post has a parent that links back to the post it is a reply
|
14
|
+
# to:
|
15
|
+
#
|
16
|
+
# parent = children.first.parent
|
17
|
+
#
|
18
|
+
# A post also has an account_record, which is the ActiveRecord version of the
|
19
|
+
# account string:
|
20
|
+
#
|
21
|
+
# account = post.account_record
|
22
|
+
#
|
23
|
+
# A post has many followers, which are the accounts that follow the author:
|
24
|
+
#
|
25
|
+
# followers = post.followers
|
26
|
+
#
|
27
|
+
# A post belongs to a community, which can be accessed as community_record,
|
28
|
+
# the ActiveRecord version of the community string:
|
29
|
+
#
|
30
|
+
# communit = post.community_record
|
31
|
+
#
|
32
|
+
# A post has many flaggers, which are the accounts that have flagged this
|
33
|
+
# post:
|
34
|
+
#
|
35
|
+
# flaggers = post.flaggers
|
36
|
+
#
|
37
|
+
# A post has many rebloggers, which are the accounts that have reblogged this
|
38
|
+
# post:
|
39
|
+
#
|
40
|
+
# rebloggers = post.rebloggers
|
41
|
+
#
|
42
|
+
# A post has many promoters, which are the accounts that have promoted this
|
43
|
+
# post:
|
44
|
+
#
|
45
|
+
# promoters = post.promoters
|
46
|
+
#
|
47
|
+
# Scopes
|
48
|
+
# ======
|
49
|
+
#
|
50
|
+
# We can use these scopes to perform a lookup on posts:
|
51
|
+
#
|
52
|
+
# posts = Hive::Post.author 'alice'
|
53
|
+
# posts = Hive::Post.community 'steemit'
|
54
|
+
# posts = Hive::Post.category 'steemit'
|
55
|
+
# posts = Hive::Post.depth 0 # only returns root posts
|
56
|
+
# posts = Hive::Post.root_posts # same as depth 0
|
57
|
+
#
|
58
|
+
# Replies can be queried by specifying depth greater than zero:
|
59
|
+
#
|
60
|
+
# replies = Hive::Post.depth 1..10 # only returns replies up to 10 in depth
|
61
|
+
# replies = Hive::Post.depth 1..255 # only returns replies
|
62
|
+
# replies = Hive::Post.replies # same as depth 1..255
|
63
|
+
#
|
64
|
+
# We can also specify replies for a particular author, which is analogous to
|
65
|
+
# all replies to an author on steemit.com (i.e.: https://steemit.com/@alice/recent-replies):
|
66
|
+
#
|
67
|
+
# replies = Hive::Post.replies(parent_author: 'alice')
|
68
|
+
#
|
69
|
+
# We can query for posts that were reblogged by someone:
|
70
|
+
#
|
71
|
+
# posts = Hive::Post.rebloggers('alice')
|
72
|
+
#
|
73
|
+
# If we want to grab all of the posts that have *all* of these tags:
|
74
|
+
#
|
75
|
+
# posts = Hive::Post.tagged(all: %w(steemit steem video youtube))
|
76
|
+
#
|
77
|
+
# Or we can grab all of the posts with *any* of these tags:
|
78
|
+
#
|
79
|
+
# posts = Hive::Post.tagged(any: %w(steemit steem video youtube))
|
80
|
+
#
|
81
|
+
# Or, which is the same as using any:
|
82
|
+
#
|
83
|
+
# posts = Hive::Post.tagged(%w(steemit steem video youtube))
|
84
|
+
#
|
85
|
+
# Here, we can find all of the posts that mention *all* of these accounts:
|
86
|
+
#
|
87
|
+
# posts = Hive::Post.mentioned(all: %w(alice bob))
|
88
|
+
#
|
89
|
+
# Or find all of the posts that mention *any* of these accounts:
|
90
|
+
#
|
91
|
+
# posts = Hive::Post.mentioned(any: %w(alice bob))
|
92
|
+
#
|
93
|
+
# We can order by (which automatically joins {Hive::PostsCache}):
|
94
|
+
#
|
95
|
+
# posts = Hive::Post.order_by_payout(:asc)
|
96
|
+
# posts = Hive::Post.order_by_payout # same as order_by_payout(:asc)
|
97
|
+
# posts = Hive::Post.order_by_payout(:desc)
|
98
|
+
# posts = Hive::Post.order_by_children
|
99
|
+
# posts = Hive::Post.order_by_author_rep
|
100
|
+
# posts = Hive::Post.order_by_total_votes
|
101
|
+
# posts = Hive::Post.order_by_up_votes
|
102
|
+
# posts = Hive::Post.order_by_promoted
|
103
|
+
# posts = Hive::Post.order_by_created_at
|
104
|
+
# posts = Hive::Post.order_by_payout_at
|
105
|
+
# posts = Hive::Post.order_by_updated_at
|
106
|
+
# posts = Hive::Post.order_by_rshares # first result is the most flagged post!
|
107
|
+
#
|
108
|
+
# Other scopes:
|
109
|
+
#
|
110
|
+
# posts = Hive::Post.deleted
|
111
|
+
# posts = Hive::Post.deleted(false)
|
112
|
+
# posts = Hive::Post.pinned
|
113
|
+
# posts = Hive::Post.pinned(false)
|
114
|
+
# posts = Hive::Post.muted
|
115
|
+
# posts = Hive::Post.muted(false)
|
116
|
+
# posts = Hive::Post.valid
|
117
|
+
# posts = Hive::Post.valid(false)
|
118
|
+
# posts = Hive::Post.promoted
|
119
|
+
# posts = Hive::Post.promoted(false)
|
120
|
+
# posts = Hive::Post.reblogged
|
121
|
+
# posts = Hive::Post.reblogged(false)
|
122
|
+
# posts = Hive::Post.after(7.days.ago)
|
123
|
+
# posts = Hive::Post.after(7.days.ago, invert: true)
|
124
|
+
# posts = Hive::Post.before(7.days.ago)
|
125
|
+
# posts = Hive::Post.before(7.days.ago, invert: true)
|
126
|
+
# posts = Hive::Post.updated_after(7.days.ago)
|
127
|
+
# posts = Hive::Post.updated_after(7.days.ago, invert: true)
|
128
|
+
# posts = Hive::Post.updated_before(7.days.ago)
|
129
|
+
# posts = Hive::Post.updated_before(7.days.ago, invert: true)
|
130
|
+
# posts = Hive::Post.payout_after(7.days.ago)
|
131
|
+
# posts = Hive::Post.payout_after(7.days.ago, invert: true)
|
132
|
+
# posts = Hive::Post.payout_before(7.days.ago)
|
133
|
+
# posts = Hive::Post.payout_before(7.days.ago, invert: true)
|
134
|
+
# posts = Hive::Post.payout_zero
|
135
|
+
# posts = Hive::Post.payout_zero(false)
|
136
|
+
# posts = Hive::Post.app('busy')
|
137
|
+
# posts = Hive::Post.app('busy', version: '1.0')
|
138
|
+
# posts = Hive::Post.app('busy', version: '1.0', invert: true)
|
139
|
+
# posts = Hive::Post.format('markdown')
|
140
|
+
# posts = Hive::Post.format('markdown', invert: false)
|
141
|
+
#
|
142
|
+
# All scopes can be strung together, e.g.:
|
143
|
+
#
|
144
|
+
# posts = Hive::Post.root_posts.author('alice').promoted
|
145
|
+
# posts = Hive::Post.replies(parent_author: 'alice').community('steemit')
|
146
|
+
# posts = Hive::Post.category('steemit').depth(255).valid
|
147
|
+
# posts = Hive::Post.tagged(all: %w(radiator ruby)).where.not(author: 'inertia')
|
148
|
+
# posts = Hive::Post.mentioned(all: %w(inertia whatsup)).tagged(any: %w(community shoutout))
|
149
|
+
#
|
150
|
+
# Most interesting queries (once your node is fully synchronized):
|
151
|
+
#
|
152
|
+
# Hive::Post.order_by_rshares.deleted.first # o_O mfw
|
153
|
+
class Post < Base
|
154
|
+
# Blockchain limit on depth.
|
155
|
+
MAX_HARD_DEPTH = 65535
|
156
|
+
# Witness limit on depth.
|
157
|
+
MAX_SOFT_DEPTH = 255
|
158
|
+
|
159
|
+
self.table_name = :hive_posts
|
160
|
+
|
161
|
+
has_one :cache, class_name: 'PostsCache'
|
162
|
+
belongs_to :parent, class_name: 'Post'
|
163
|
+
has_many :children, -> { depth(1..MAX_SOFT_DEPTH) },
|
164
|
+
inverse_of: 'parent', foreign_key: :parent_id, class_name: 'Post'
|
165
|
+
|
166
|
+
belongs_to :author_account, primary_key: :name, foreign_key: :author, class_name: 'Account'
|
167
|
+
has_many :followers, through: :author_account, source: :followers
|
168
|
+
|
169
|
+
belongs_to :community_record, primary_key: :name, foreign_key: :community, class_name: 'Community'
|
170
|
+
has_many :community_members, through: :community_record, source: :members
|
171
|
+
has_many :community_accounts, through: :community_record, source: :accounts
|
172
|
+
|
173
|
+
has_many :flags
|
174
|
+
has_many :flaggers, through: :flags, source: :account_record
|
175
|
+
has_many :reblogs
|
176
|
+
has_many :rebloggers, through: :reblogs
|
177
|
+
has_many :post_tags
|
178
|
+
|
179
|
+
has_many :payments
|
180
|
+
has_many :promoters, through: :payments, source: :from
|
181
|
+
|
182
|
+
has_many :feed_cache
|
183
|
+
has_many :feed_accounts, through: :feed_cache, source: :account
|
184
|
+
|
185
|
+
scope :author, lambda { |author| where(author: author) }
|
186
|
+
scope :community, lambda { |community| where(community: community) }
|
187
|
+
scope :category, lambda { |category| where(category: category) }
|
188
|
+
scope :depth, lambda { |depth, options = {invert: false}|
|
189
|
+
r = if depth == 0
|
190
|
+
where(depth: depth, parent: nil)
|
191
|
+
else
|
192
|
+
where(depth: depth).where.not(parent: nil)
|
193
|
+
end
|
194
|
+
|
195
|
+
r = all.where.not(id: r) if !!options[:invert]
|
196
|
+
|
197
|
+
r
|
198
|
+
}
|
199
|
+
scope :root_posts, -> { depth(0) }
|
200
|
+
scope :replies, lambda { |options = {invert: false}|
|
201
|
+
r = depth(1..MAX_SOFT_DEPTH)
|
202
|
+
|
203
|
+
if !!options[:parent_author] || !!options[:parent_permlink]
|
204
|
+
parent_posts = all
|
205
|
+
|
206
|
+
if !!options[:parent_author]
|
207
|
+
parent_author = Post::transform_account_selector options[:parent_author]
|
208
|
+
parent_posts = parent_posts.where(author: parent_author)
|
209
|
+
end
|
210
|
+
|
211
|
+
if !!options[:parent_permlink]
|
212
|
+
parent_posts = parent_posts.where(permlink: options[:parent_permlink])
|
213
|
+
end
|
214
|
+
|
215
|
+
r = r.where(parent_id: parent_posts)
|
216
|
+
end
|
217
|
+
|
218
|
+
r = all.where.not(id: r) if !!options[:invert]
|
219
|
+
|
220
|
+
r
|
221
|
+
}
|
222
|
+
scope :deleted, lambda { |deleted = true| where(is_deleted: deleted) }
|
223
|
+
scope :pinned, lambda { |pinned = true| where(is_pinned: pinned) }
|
224
|
+
scope :muted, lambda { |muted = true| where(is_muted: muted) }
|
225
|
+
scope :valid, lambda { |valid = true| where(is_valid: valid) }
|
226
|
+
scope :promoted, lambda { |promoted = true|
|
227
|
+
if promoted
|
228
|
+
where.not(promoted: 0.0)
|
229
|
+
else
|
230
|
+
where(promoted: 0.0)
|
231
|
+
end
|
232
|
+
}
|
233
|
+
scope :reblogged, lambda { |reblogged = true|
|
234
|
+
if reblogged
|
235
|
+
where(id: Reblog.select(:post_id))
|
236
|
+
else
|
237
|
+
where.not(id: Reblog.select(:post_id))
|
238
|
+
end
|
239
|
+
}
|
240
|
+
scope :rebloggers, lambda { |rebloggers = []|
|
241
|
+
reblogs = Reblog.where(account: rebloggers)
|
242
|
+
reblogged.where(id: reblogs.select(:post_id))
|
243
|
+
}
|
244
|
+
|
245
|
+
scope :feed_cache, lambda { |account, options = {invert: false}|
|
246
|
+
r = if account.is_a? ActiveRecord::Relation
|
247
|
+
root_posts.joins(:feed_cache).
|
248
|
+
where('hive_feed_cache.account_id IN(?)', account.select(:id))
|
249
|
+
else
|
250
|
+
account = Post::transform_account_selector account
|
251
|
+
root_posts.joins(:author_account, :feed_cache).
|
252
|
+
where('hive_accounts.name IN(?)', account)
|
253
|
+
end
|
254
|
+
|
255
|
+
if !!options[:invert]
|
256
|
+
all.root_posts.where.not(id: r)
|
257
|
+
else
|
258
|
+
r
|
259
|
+
end
|
260
|
+
}
|
261
|
+
|
262
|
+
|
263
|
+
scope :feed, lambda { |account, options = {invert: false}|
|
264
|
+
account = Post::transform_account_selector account
|
265
|
+
r = root_posts.joins(:author_account, :rebloggers).
|
266
|
+
where('hive_accounts.name IN(?) OR hive_reblogs.account IN(?)', account, account)
|
267
|
+
|
268
|
+
if !!options[:invert]
|
269
|
+
all.root_posts.where.not(id: r)
|
270
|
+
else
|
271
|
+
r
|
272
|
+
end
|
273
|
+
}
|
274
|
+
|
275
|
+
scope :tagged, lambda { |*args|
|
276
|
+
options = if args[0].nil? || args[0].is_a?(Hash)
|
277
|
+
args[0] || {}
|
278
|
+
else
|
279
|
+
{any: args}
|
280
|
+
end
|
281
|
+
relation = all
|
282
|
+
|
283
|
+
if !!options[:all]
|
284
|
+
[options[:all]].flatten.map do |tag|
|
285
|
+
selector = Hive::PostTag.where(tag: tag).select(:post_id)
|
286
|
+
relation = relation.where(id: selector)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
if !!options[:any]
|
291
|
+
relation = relation.where(id: Hive::PostTag.where(tag: [options[:any]].flatten).select(:post_id))
|
292
|
+
end
|
293
|
+
|
294
|
+
if !!options[:invert]
|
295
|
+
where.not(id: relation)
|
296
|
+
else
|
297
|
+
relation
|
298
|
+
end
|
299
|
+
}
|
300
|
+
|
301
|
+
scope :mentioned, lambda { |options = {}|
|
302
|
+
where(id: Hive::PostsCache.mentioned(options).select(:post_id))
|
303
|
+
}
|
304
|
+
|
305
|
+
scope :after, lambda { |after, options = {invert: false}|
|
306
|
+
invertable 'hive_posts.created_at > ?', after, options
|
307
|
+
}
|
308
|
+
|
309
|
+
scope :before, lambda { |before, options = {invert: false}|
|
310
|
+
invertable 'hive_posts.created_at < ?', before, options
|
311
|
+
}
|
312
|
+
|
313
|
+
scope :updated_after, lambda { |after, options = {invert: false}|
|
314
|
+
where(id: Hive::PostsCache.updated_after(after, options).select(:post_id))
|
315
|
+
}
|
316
|
+
|
317
|
+
scope :updated_before, lambda { |before, options = {invert: false}|
|
318
|
+
where(id: Hive::PostsCache.updated_before(before, options).select(:post_id))
|
319
|
+
}
|
320
|
+
|
321
|
+
scope :payout_after, lambda { |after, options = {invert: false}|
|
322
|
+
where(id: Hive::PostsCache.payout_after(after, options).select(:post_id))
|
323
|
+
}
|
324
|
+
|
325
|
+
scope :payout_before, lambda { |before, options = {invert: false}|
|
326
|
+
where(id: Hive::PostsCache.payout_before(before, options).select(:post_id))
|
327
|
+
}
|
328
|
+
|
329
|
+
scope :payout_zero, lambda { |payout_zero = true|
|
330
|
+
where(id: Hive::PostsCache.payout_zero(payout_zero).select(:post_id))
|
331
|
+
}
|
332
|
+
|
333
|
+
scope :app, lambda { |app, options = {version: nil, invert: false}|
|
334
|
+
where(id: Hive::PostsCache.app(app, options).select(:post_id))
|
335
|
+
}
|
336
|
+
|
337
|
+
scope :format, lambda { |format, options = {invert: false}|
|
338
|
+
where(id: Hive::PostsCache.format(format, options).select(:post_id))
|
339
|
+
}
|
340
|
+
|
341
|
+
scope :order_by_cache, lambda { |*args|
|
342
|
+
field = args[0].keys.first rescue raise('Order field must be specified.')
|
343
|
+
direction = args[0].values.first || :asc
|
344
|
+
table = Hive::PostsCache.arel_table
|
345
|
+
|
346
|
+
joins(:cache).order(table[field.to_sym].send(direction.to_sym))
|
347
|
+
}
|
348
|
+
|
349
|
+
scope :order_by_feed_cache, lambda { |*args|
|
350
|
+
field = args[0].keys.first rescue raise('Order field must be specified.')
|
351
|
+
direction = args[0].values.first || :asc
|
352
|
+
table = Hive::FeedCache.arel_table
|
353
|
+
|
354
|
+
joins(:feed_cache).order(table[field.to_sym].send(direction.to_sym))
|
355
|
+
}
|
356
|
+
|
357
|
+
scope :order_by_payout, lambda { |*args| order_by_cache(payout: args[0] || :asc) }
|
358
|
+
scope :order_by_children, lambda { |*args| order_by_cache(children: args[0] || :asc) }
|
359
|
+
scope :order_by_author_rep, lambda { |*args| order_by_cache(author_rep: args[0] || :asc) }
|
360
|
+
scope :order_by_total_votes, lambda { |*args| order_by_cache(total_votes: args[0] || :asc) }
|
361
|
+
scope :order_by_up_votes, lambda { |*args| order_by_cache(up_votes: args[0] || :asc) }
|
362
|
+
scope :order_by_promoted, lambda { |*args| order_by_cache(promoted: args[0] || :asc) }
|
363
|
+
scope :order_by_created_at, lambda { |*args| order_by_cache(created_at: args[0] || :asc) }
|
364
|
+
scope :order_by_payout_at, lambda { |*args| order_by_cache(payout_at: args[0] || :asc) }
|
365
|
+
scope :order_by_updated_at, lambda { |*args| order_by_cache(updated_at: args[0] || :asc) }
|
366
|
+
scope :order_by_rshares, lambda { |*args| order_by_cache(rshares: args[0] || :asc) }
|
367
|
+
scope :order_by_feed_cache_created_at, lambda { |*args| order_by_feed_cache(created_at: args[0] || :asc) }
|
368
|
+
|
369
|
+
# Finds a post by slug (or even URL).
|
370
|
+
#
|
371
|
+
# @param slug String A composite of author and permlink that uniquely identifies a post. E.g.: "@steemit/firstpost"
|
372
|
+
# @return <Hive::Post>
|
373
|
+
def self.find_by_slug(slug)
|
374
|
+
slug = slug.split('@').last
|
375
|
+
slug = slug.split('/')
|
376
|
+
author = slug[0]
|
377
|
+
permlink = slug[1..-1].join('/')
|
378
|
+
|
379
|
+
Post.where(author: author, permlink: permlink).first
|
380
|
+
end
|
381
|
+
|
382
|
+
# All tags related to this post.
|
383
|
+
#
|
384
|
+
# @return <String>
|
385
|
+
def tags; post_tags.pluck(:tag); end
|
386
|
+
|
387
|
+
# The entire discussion related to this post including all children and
|
388
|
+
# grandchildren replies (the result also includes this post).
|
389
|
+
#
|
390
|
+
# @return {ActiveRecord::Relation}
|
391
|
+
def discussion
|
392
|
+
clause = <<-DONE
|
393
|
+
hive_posts.community = ?
|
394
|
+
AND hive_posts.category = ?
|
395
|
+
AND hive_posts.id >= ?
|
396
|
+
AND hive_posts.id IN(?)
|
397
|
+
DONE
|
398
|
+
|
399
|
+
Post.deleted(false).
|
400
|
+
where(clause, community, category, id, discussion_ids)
|
401
|
+
end
|
402
|
+
|
403
|
+
# Returns the unique string containing the author and permlink, useful in
|
404
|
+
# restful development as param_id, if desired.
|
405
|
+
#
|
406
|
+
# @return String
|
407
|
+
def slug
|
408
|
+
"@#{author}/#{permlink}"
|
409
|
+
end
|
410
|
+
private
|
411
|
+
# @private
|
412
|
+
def discussion_ids(ids = [id])
|
413
|
+
new_ids = Post.deleted(false).depth(1..MAX_SOFT_DEPTH).
|
414
|
+
where(community: community, category: category).
|
415
|
+
where(parent_id: ids).pluck(:id)
|
416
|
+
|
417
|
+
ids += new_ids + discussion_ids(new_ids) if new_ids.any?
|
418
|
+
|
419
|
+
ids.uniq.compact
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|