Skip to content

Instantly share code, notes, and snippets.

@ayrton
Last active September 29, 2020 20:42
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save ayrton/4351545 to your computer and use it in GitHub Desktop.
Save ayrton/4351545 to your computer and use it in GitHub Desktop.
Testing Active Support concerns with rspec in isolation. See first comment for more information.
module Taggable
extend ActiveSupport::Concern
included do
attr_accessible :tags
after_save :set_tags
after_destroy :unset_tags
end
module ClassMethods
def tags
$redis.smembers("#{self.table_name}:tags").sort
end
end
def tags=(tags)
@tags = tags.map(&:downcase)
end
def tags
@tags ||= $redis.smembers("#{self.class.table_name}:#{id}:tags")
end
def related_tag_ids
tags.inject([]) { |total, tag|
total |= $redis.smembers("#{self.class.table_name}:tags:#{tag}")
} - [id.to_s]
end
def find_related_tags
self.class.where(id: related_tag_ids)
end
private
def set_tags
return if tags.empty?
$redis.sadd("#{self.class.table_name}:tags", tags)
$redis.sadd("#{self.class.table_name}:#{id}:tags", tags)
tags.each do |tag|
$redis.sadd("#{self.class.table_name}:tags:#{tag}", id)
end
end
def unset_tags
return if tags.empty?
tags.each do |tag|
$redis.srem("#{self.class.table_name}:tags:#{tag}", id)
$redis.srem("#{self.class.table_name}:tags", tag) if $redis.scard("#{self.class.table_name}:tags:#{tag}").zero?
end
$redis.del("#{self.class.table_name}:#{id}:tags")
end
end
require 'spec_helper'
describe Taggable do
let!(:t0) { Train.create(tags: %w(rails track)) }
let!(:t1) { Train.create(tags: %w(track cargo)) }
let!(:t2) { Train.create(tags: %w(vehicle)) }
let!(:t3) { Train.create(tags: %w(RaIlS VEHICLE)) }
describe 'class methods' do
describe 'tags' do
it 'returns the four tags sorted' do
expect(Train.tags).to eq(%w(cargo rails track vehicle))
end
end
end
describe 'instance methods' do
describe 'related_tag_ids' do
it { expect(t0.related_tag_ids.map(&:to_i)).to include(t1.id, t3.id) }
it { expect(t0.related_tag_ids.map(&:to_i)).not_to include(t0.id, t2.id) }
end
end
# ...
end
class Train
include ActiveModel::MassAssignmentSecurity, ActiveSupport::Callbacks
define_callbacks :create, :destroy
def self.after_create(*filters, &blk)
set_callback(:create, :after, *filters, &blk)
end
def self.create(args = {})
new(args).tap do |obj|
obj.create
end
end
def self.before_destroy(*filters, &blk)
set_callback(:destroy, :before, *filters, &blk)
end
def self.table_name
'trains'
end
class_attribute :instances
attr_accessor :id
def initialize(args)
self.class.instances ||= {}
self.id = self.class.instances.keys.empty? ? 1 : self.class.instances.keys.last + 1
args.each do |attr, value|
self.send("#{attr}=".to_sym, value)
end
self.class.instances = { id => self }
end
def create
run_callbacks :create
end
def destroy
run_callbacks :destroy
end
include Taggable
end
@ayrton
Copy link
Author

ayrton commented Dec 21, 2012

Testing Active Support concerns with rspec in isolation.

Taggable is our concern, Train is our independent class that outlays the dependencies of our concern.
And finally we have our spec, which tests our concern using the Train class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment