Commit 104a77f7 by Ivan Lan

Initial commit

parents
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
source 'https://rubygems.org'
# Specify your gem's dependencies in whaleback.gemspec
gemspec
# Whaleback
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/whaleback`. To experiment with that code, run `bin/console` for an interactive prompt.
TODO: Delete this and the text above, and describe your gem
## Usage
Copy migrations to your project
$ rails g acts_as_pasting:migrations
TODO: Write usage instructions here
require "bundler/gem_tasks"
task :default => :spec
#!/usr/bin/env ruby
require "bundler/setup"
require "whaleback"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require "irb"
IRB.start(__FILE__)
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here
class CreateActsAsPastingPastings < ActiveRecord::Migration[5.2]
def change
create_table :acts_as_pasting_pastings do |t|
t.references :pasteable, polymorphic: true, index: { name: 'acts_as_pasting_pasteable' }
t.references :pasted, polymorphic: true, index: { name: 'acts_as_pasting_pasted' }
t.string :type, index: true
t.timestamps
end
end
end
require "acts_as_pasting/version"
module ActsAsPasting
require 'acts_as_pasting/errors'
autoload :Pasted, 'acts_as_pasting/pasted'
autoload :Pasting, 'acts_as_pasting/pasting'
def self.table_name_prefix
'acts_as_pasting_'
end
end
module ActsAsPasting
class Error < StandardError
end
module Errors
class BaseError < ActsAsPasting::Error
attr_accessor :message, :code, :status
def initialize message, code, status
super(message)
@message, @code, @status = message, code, status.to_i
end
end
end
end
\ No newline at end of file
module ActsAsPasting
module Pasted
extend ActiveSupport::Concern
included do
has_many :pastings, as: :pasted
# ary => [[klass, id], [klass, id]]
# ary => [obj, obj]
scope :pasted_with_any, ->(ary) {
ary = parsed_condition_ary(ary)
ary.map { |klass, id| joins(:pastings).where(
'pastings.pasteable_type = ? AND pastings.pasteable_id = ?',
klass.constantize.base_class.name, id
)
}.reduce(:or)&.distinct || self.none
}
scope :pasted_with_all, ->(ary) {
ary = parsed_condition_ary(ary)
ids = ary.map do |klass, id|
joins(:pastings).where(pastings: {pasteable_type: klass.constantize.base_class.name, pasteable_id: id}).pluck(:id)
end.reduce(:&)
where(id: ids)
}
after_create :save_paste_list
def paste_list
pastings.reload.map(&:pasteable)
end
def paste_list_for klass_name
klass_name.constantize.where(
id: pastings.where(pasteable_type: klass_name.constantize.base_class.name).pluck(:pasteable_id)
)
end
def paste_list_names
paste_list.map(&:name)
end
def paste_list_add obj
pastings.create!(pasted: self, pasteable: obj)
end
def paste_list_remove obj
pastings.where(pasted: self, pasteable: obj).destroy_all
end
# ary => [[klass, id], [klass, id]]
# ary => [obj, obj]
def paste_list= ary
ary = self.class.parsed_condition_ary(ary)
exist_ary = pastings.where(pasted: self).pluck(:pasteable_type, :pasteable_id)
add_ary = ary - exist_ary
remove_ary = exist_ary - ary
if new_record?
@paste_list = ary
else
self.class.transaction do
remove_ary.each { |klass, id| pastings.where(
pasted: self,
pasteable_type: klass,
pasteable_id: id
).destroy_all
}
add_ary.each { |klass, id| pastings.create!(
pasted: self,
pasteable_type: klass,
pasteable_id: id
)
}
end
end
end
def method_missing name, *arg, &block
klass_downcase = /^paste_(.+)_list$/.match(name)&.[](1)
if klass_downcase
downcase_all_combination(klass_downcase).each do |str|
return paste_list_for(str) if correct_class_name?(str)
end
end
super(name, *arg, &block)
end
private
def save_paste_list
if @paste_list
self.paste_list = @paste_list
end
end
def correct_class_name? name
ActiveRecord::Base === name.safe_constantize.try(:new)
end
def downcase_all_combination str
downcase_all_combination_ary(str).reverse.map { |x| (String === x ? x : x.flatten.join).classify }
end
def downcase_all_combination_ary str
items = String === str ? str.split('_') : str
return items if items.length == 1
l = downcase_all_combination_ary(items[1..-1])
result = []
l.each do |i|
result.push(items[0, 1] << '/' << i)
result.push(items[0, 1] << '_' << i)
end
result
end
end
module ClassMethods
def pasted_with *klasses
klasses.each do |klass|
downcase = klass.name.underscore.gsub('/', '_')
base_class_name = klass.base_class.name
class_eval <<-RUBY, __FILE__, __LINE__ + 1
after_create :save_paste_#{downcase}_list
def paste_#{downcase}_list
paste_list_for("#{klass}")
end
def paste_#{downcase}_ids= ids
ary = ids.map { |id| ["#{base_class_name}", id] }
self.paste_#{downcase}_list = ary
end
def paste_#{downcase}_list= ary, run_save: true
ary = self.class.parsed_condition_ary(ary).select { |a|
a.first == "#{base_class_name}"
}
if new_record?
@paste_#{downcase}_list = ary
else
exist_ary = pastings.where(pasted: self, pasteable_type: "#{base_class_name}").pluck(:pasteable_type, :pasteable_id)
add_ary = ary - exist_ary
remove_ary = exist_ary - ary
self.class.transaction do
remove_ary.each { |klass, id| pastings.where(
pasted: self,
pasteable_type: klass,
pasteable_id: id
).destroy_all
}
add_ary.each { |klass, id| pastings.create!(
pasted: self,
pasteable_type: klass,
pasteable_id: id
)
}
end
end
end
private
def save_paste_#{downcase}_list
if @paste_#{downcase}_list
self.paste_#{downcase}_list = @paste_#{downcase}_list
end
end
RUBY
end
end
def parsed_condition_ary ary
case ary.first
when ActiveRecord::Base
ary.map { |obj| [obj.class.base_class.name, obj.id] }
when Hash
ary.map { |h| [h[:paste_type].constantize.base_class.name, h[:paste_id]] }
else
ary.map { |a| [a.first.constantize.base_class.name, a.last] }
end
end
def acts_as_pasted associations, options={}
has_many associations, through: :pastings, source: :pasteable, **options
end
end
module Association
def acts_as_pasted associations, options={}
has_many :pastings, as: :pasteable
has_many associations, through: :pastings, source: :pasted, **options
end
end
end
end
# == Schema Information
#
# Table name: pastings
#
# id :bigint not null, primary key
# pasteable_type :string(255)
# pasteable_id :bigint
# pasted_type :string(255)
# pasted_id :bigint
# type :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# pasting_type :string(255)
#
# Indexes
#
# index_pastings_on_pasteable_type_and_pasteable_id (pasteable_type,pasteable_id)
# index_pastings_on_pasted_type_and_pasted_id (pasted_type,pasted_id)
# index_pastings_on_type (type)
#
module ActsAsPasting
class Pasting < ApplicationRecord
belongs_to :pasteable, polymorphic: true
belongs_to :pasted, polymorphic: true
end
end
module ActsAsPasting
VERSION = "0.1.0"
end
require 'rails/generators/migration'
module ActsAsPasting
module Generators
class MigrationsGenerator < Rails::Generators::Base
include Rails::Generators::Migration
desc 'Copy whaleback migrations to your application.'
def self.next_migration_number(dir)
sleep 1
Time.now.utc.strftime("%Y%m%d%H%M%S")
end
source_root File.expand_path("../../../../db/migrate", __FILE__)
def copy_migrations
# Use sort() to order the migrations by seq
# Use [2..-1] to delete the seq
Dir[ File.join(self.class.source_root, '*.rb') ].sort.each { |f|
copy_migration File.basename(f, '.rb')
}
end
protected
def copy_migration(filename)
if self.class.migration_exists?("db/migrate", "#{filename[2..-1]}")
say_status("skipped", "Migration #{filename[2..-1]} already exists")
else
migration_template "#{filename}.rb", "db/migrate/#{filename[2..-1]}.rb"
end
end
end
end
end
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'acts_as_pasting/version'
Gem::Specification.new do |spec|
spec.name = 'acts_as_pasting'
spec.version = ActsAsPasting::VERSION
spec.authors = ['ivan Lan']
spec.email = ['mumumumushu@gmail.com']
spec.summary = %q{}
spec.description = %q{}
spec.homepage = 'https://git.tallty.com/open-source/acts_as_pasting'
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host.
if spec.respond_to?(:metadata)
spec.metadata['allowed_push_host'] = 'https://git.tallty.com/open-source/acts_as_pasting'
else
raise 'RubyGems 2.0 or newer is required to protect against ' \
'public gem pushes.'
end
# spec.files = `git ls-files -z`.split('\x0').reject do |f|
# f.match(%r{^(test|spec|features)/})
# end
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']
spec.add_development_dependency('bundler', '~> 1.14')
spec.add_development_dependency('rake', '~> 10.0')
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment