module ActionStore::Mixin::ClassMethods

Attributes

defined_actions[R]

Public Instance Methods

action_store(action_type, name, class_name: nil, action_class_name: "Action", counter_cache: nil, user_counter_cache: nil) click to toggle source
# File lib/action_store/mixin.rb, line 36
def action_store(action_type, name, class_name: nil, action_class_name: "Action", counter_cache: nil, user_counter_cache: nil)
  name = name.to_s

  class_name ||= name.classify
  target_klass = class_name.constantize
  action_klass = action_class_name.constantize
  action_type = action_type.to_s
  if counter_cache == true
    # @post.stars_count
    counter_cache = "#{action_type.pluralize}_count"
  end
  if user_counter_cache == true
    # @user.star_posts_count
    user_counter_cache = "#{action_type}_#{name.pluralize}_count"
  end

  @defined_actions ||= []
  action = {
    action_name: name,
    action_type: action_type,
    action_klass: action_klass,
    target_klass: target_klass,
    target_type: target_klass.name,
    counter_cache: counter_cache,
    user_counter_cache: user_counter_cache
  }
  @defined_actions << action

  define_relations(action_klass: action_klass, target_klass: target_klass,
                   action_type: action_type, action_name: name)
end
create_action(action_type, opts) click to toggle source
# File lib/action_store/mixin.rb, line 80
def create_action(action_type, opts)
  opts[:action_type] = action_type
  opts = safe_action_opts(opts)
  return false if opts[:user_id].blank? || opts[:user_type].blank?
  return false if opts[:target_id].blank? || opts[:target_type].blank?

  defined_action = find_defined_action(opts[:action_type], opts[:target_type])
  return false if defined_action.nil?

  # create! for raise RecordNotUnique
  begin
    action = defined_action[:action_klass].find_or_create_by!(where_opts(opts))
    action.update(action_option: opts[:action_option]) if opts.key?(:action_option)
  rescue ActiveRecord::RecordNotUnique
    # update action_option on exist
    action = defined_action[:action_klass].where(where_opts(opts)).take
    action.update(action_option: opts[:action_option]) if opts.key?(:action_option)
  end

  reset_counter_cache(action, defined_action)
  true
end
destroy_action(action_type, opts) click to toggle source
# File lib/action_store/mixin.rb, line 103
def destroy_action(action_type, opts)
  opts[:action_type] = action_type
  opts = safe_action_opts(opts)
  return false if opts[:user_id].blank? || opts[:user_type].blank?
  return false if opts[:target_id].blank? || opts[:target_type].blank?

  defined_action = find_defined_action(opts[:action_type], opts[:target_type])
  return false if defined_action.nil?

  action = defined_action[:action_klass].where(where_opts(opts)).first
  return true unless action

  action.destroy
  reset_counter_cache(action, defined_action)
  true
end
find_action(action_type, opts) click to toggle source
# File lib/action_store/mixin.rb, line 68
def find_action(action_type, opts)
  opts[:action_type] = action_type
  opts = safe_action_opts(opts)
  return nil if opts[:user_id].blank? || opts[:user_type].blank?
  return nil if opts[:target_id].blank? || opts[:target_type].blank?

  defined_action = find_defined_action(opts[:action_type], opts[:target_type])
  return nil if defined_action.nil?

  defined_action[:action_klass].find_by(where_opts(opts))
end
find_defined_action(action_type, target_type) click to toggle source
# File lib/action_store/mixin.rb, line 28
def find_defined_action(action_type, target_type)
  action_type = action_type.to_s
  name = target_type.to_s.singularize.underscore
  defined_actions.find do |a|
    a[:action_type] == action_type && (a[:action_name] == name || a[:target_type] == target_type)
  end
end
reset_counter_cache(action, defined_action) click to toggle source
# File lib/action_store/mixin.rb, line 120
def reset_counter_cache(action, defined_action)
  return false if action.blank?

  if defined_action[:counter_cache] && action.target.present?
    target_count = defined_action[:action_klass].where(
      action_type: defined_action[:action_type],
      target_type: action.target_type,
      target_id: action.target_id
    ).count
    action.target.update_attribute(defined_action[:counter_cache], target_count)
  end

  if defined_action[:user_counter_cache] && action.user.present?
    user_count = defined_action[:action_klass].where(
      action_type: defined_action[:action_type],
      target_type: action.target_type,
      user_type: action.user_type,
      user_id: action.user_id
    ).count
    action.user.update_attribute(defined_action[:user_counter_cache], user_count)
  end
end

Private Instance Methods

define_relations(target_klass:, action_klass:, action_type:, action_name:) click to toggle source
# File lib/action_store/mixin.rb, line 145
def define_relations(target_klass:, action_klass:, action_type:, action_name:)
  user_klass = self

  # user, person
  user_name = user_klass.table_name.underscore.singularize

  # like_topic, follow_user
  full_action_name = [action_type, action_name].join("_")
  # like_user, follow_user
  full_action_name_for_target = [action_type, "by", user_name].join("_")
  # unlike_topic, unfollow_user
  unaction_name = "un#{full_action_name}"

  # @target.like_topic_actions, @target.follow_user_actions
  has_many_name = [full_action_name, "actions"].join("_").to_sym
  # @target.like_topics, @target.follow_users
  has_many_through_name = full_action_name.pluralize.to_sym

  # @user.like_by_user_actions, @user.follow_by_user_actions
  has_many_name_for_target = [full_action_name_for_target, "actions"].join("_").to_sym
  # @user.like_by_users, @user.follow_by_users
  has_many_through_name_for_target = full_action_name_for_target.pluralize.to_sym

  has_many_scope = lambda {
    where(action_type: action_type, target_type: target_klass.name, user_type: user_klass.name)
  }

  # User has_many :like_topic_actions
  user_klass.send :has_many, has_many_name, has_many_scope,
                  class_name: action_klass.name,
                  foreign_key: "user_id"
  # User has_many :like_topics
  user_klass.send :has_many, has_many_through_name,
                  through: has_many_name,
                  source: :target,
                  source_type: target_klass.name

  # Topic has_many :like_user_actions
  target_klass.send :has_many, has_many_name_for_target, has_many_scope,
                    foreign_key: :target_id,
                    class_name: action_klass.name
  # Topic has_many :like_users
  target_klass.send :has_many, has_many_through_name_for_target,
                    through: has_many_name_for_target,
                    source_type: user_klass.name,
                    source: :user

  # @user.like_topic
  user_klass.send(:define_method, full_action_name) do |target_or_id|
    target_id = target_or_id.is_a?(target_klass) ? target_or_id.id : target_or_id
    result = user_klass.create_action(action_type, target_type: target_klass.name,
                                                   target_id: target_id,
                                                   user: self)
    target_or_id.reload if target_or_id.is_a?(target_klass)
    reload
    result
  end

  # @user.like_topic?
  user_klass.send(:define_method, "#{full_action_name}?") do |target_or_id|
    target_id = target_or_id.is_a?(target_klass) ? target_or_id.id : target_or_id
    result = user_klass.find_action(action_type, target_type: target_klass.name,
                                                 target_id: target_id,
                                                 user: self)
    result.present?
  end

  # @user.unlike_topic
  user_klass.send(:define_method, unaction_name) do |target_or_id|
    target_id = target_or_id.is_a?(target_klass) ? target_or_id.id : target_or_id
    result = user_klass.destroy_action(action_type, target_type: target_klass.name,
                                                    target_id: target_id,
                                                    user: self)
    target_or_id.reload if target_or_id.is_a?(target_klass)
    reload
    result
  end
end
safe_action_opts(opts) click to toggle source
# File lib/action_store/mixin.rb, line 224
def safe_action_opts(opts)
  opts ||= {}
  if opts[:user]
    opts[:user_id] = opts[:user].id
    opts[:user_type] = opts[:user].class.name
  end
  if opts[:target]
    opts[:target_type] = opts[:target].class.name
    opts[:target_id] = opts[:target].id
  end
  opts
end
where_opts(opts) click to toggle source
# File lib/action_store/mixin.rb, line 237
def where_opts(opts)
  opts.extract!(:action_type, :target_type, :target_id, :user_id, :user_type)
end