app/models/idea.rb
# encoding: UTF-8
class Idea < ActiveRecord::Base
# attr_accessible :title, :problem, :solution, :metrics, :deadline, :author, :design_size, :development_size, :rating, :category, :product_manager, :active_at
belongs_to :author, :class_name => 'User'
belongs_to :account
has_many :vettings, :dependent => :destroy
has_many :votes, :as => :subject, :dependent => :destroy
has_many :comments, :dependent => :destroy
has_many :attachments, :class_name => 'Attachment', :as => :owner, :dependent => :destroy
belongs_to :product_manager, :class_name => 'User'
include Notification::Base::CanBeSubject
include Traits::Idea::HasStates
include Traits::Idea::StarRating
include Traits::Shuffleable
include Idea::History::Base::HasHistory
include Idea::History::Creation::Recorder
include Idea::History::StateChange::Recorder
has_many :commenters, :class_name => 'User', :through => :comments, :source => :author
has_many :vetters, :class_name => 'User', :through => :vettings, :source => :user
has_many :backers, :class_name => 'User', :through => :votes, :source => :user
has_many :bookmarks, :class_name => 'User::Bookmark', :dependent => :destroy
has_and_belongs_to_many :participants, :class_name => 'User', :join_table => User::Bookmark.table_name #, :order => "#{User::Bookmark.quoted_table_name}.created_at"
validates_presence_of :author
validates_presence_of :account
validates_presence_of :rating
validates_presence_of :title
validates_inclusion_of :design_size, :in => 1..4, :allow_nil => true
validates_inclusion_of :development_size, :in => 1..4, :allow_nil => true
validates_inclusion_of :category, in: lambda { |idea| idea.account.categories }, allow_nil:true
default_values rating: 0
before_save :update_active_at
scope :managed_by, lambda { |user| where(product_manager_id: user) }
scope :not_vetted_by, lambda { |user| where('ideas.id NOT IN (?)', user.vetted_ideas.pluck(:id)) }
scope :undead_ideas, -> { where('state NOT IN (?)',IdeaStateMachine.state_values(:graveyarded,:archived)) }
scope :with_state, lambda { |*states|
where('ideas.state IN (?)', IdeaStateMachine.state_values(*states))
}
scope :without_state, lambda { |*states|
where('ideas.state NOT IN (?)', IdeaStateMachine.state_values(*states))
}
def self.limit_per_state(limit:10)
ids = pluck(:id, :state).group_by(&:last).values.map { |id_states| id_states.take(limit).map(&:first) }.flatten
where id:ids
end
def self.counts_per_state
Hash[
group(:state).count.map { |state_value,count| [IdeaStateMachine.state_name(state_value), count] }
]
end
# Other helpers
def sized?
design_size.present? && development_size.present?
end
def size
sized? and [design_size, development_size].max
end
def backed_by?(user)
backers.pluck(:id).include?(user.id)
end
# Search angles
def self.discussable_by(user)
user.account.ideas.without_state(:draft)
end
def self.vettable_by(user)
discussable_by(user).with_state(:submitted)
end
def self.votable_by(user)
discussable_by(user).with_state(:vetted, :voted)
end
def self.pickable_by(user)
discussable_by(user).with_state(:voted)
end
def self.approvable_by(user)
discussable_by(user).with_state(:designed)
end
def self.signoffable_by(user)
discussable_by(user).with_state(:implemented)
end
def self.buildable_by(user)
discussable_by(user).with_state(:picked, :approved, :signed_off)
end
def self.followed_by(user)
user.bookmarked_ideas
end
# Search orders
def self.by_impact
order('COALESCE(ideas.impact_cache, -1) DESC, ideas.active_at DESC')
end
def self.by_activity
order('ideas.active_at DESC')
end
def self.by_progress
order('ideas.state DESC')
end
def self.by_creation
order('ideas.created_at DESC')
end
# Search filters
def self.authored_by(user)
where(author_id: user.id)
end
def self.commented_by(user)
joins(:comments).where('comments.author_id = ?', user.id).group('ideas.id')
end
def self.vetted_by(user)
joins(:vettings).where('vettings.user_id = ?', user.id).group('ideas.id')
end
def self.backed_by(user)
# this originally looked like:
# joins(:votes).where('votes.user_id = ?', user.id).group('ideas.id')
# the new code works because of the uniqueness clause on votes (no double
# votes)
joins(:votes).where('votes.user_id = ?', user.id)
end
def self.not_backed_by(user)
ideas = self.arel_table
votes = Vote.arel_table.alias('backings')
joins_sql = ideas.
join(votes, Arel::Nodes::OuterJoin).
on(
votes[:subject_type].eq(self.name),
votes[:subject_id] .eq(ideas[:id]),
votes[:user_id] .eq(user.id)).
join_sources
joins(joins_sql).where(votes[:user_id].eq(nil))
end
# called from subresources (comments, vettings, votes, bookmarks)
def ping!
update_column :active_at, Time.current
end
protected
def update_active_at
return unless changes.any?
self.active_at = Time.current
end
end