-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
235 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# A sample Guardfile | ||
# More info at https://github.com/guard/guard#readme | ||
|
||
guard :rspec do | ||
watch(%r{^spec/.+_spec\.rb$}) | ||
watch(%r{^lib/actionmodel/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } | ||
watch('lib/actionmodel.rb') { 'spec' } | ||
watch('spec/spec_helper.rb') { 'spec' } | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
require 'actionmodel/version' | ||
require 'actionmodel/concern' | ||
|
||
module ActionModel | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,100 @@ | ||
module Concern | ||
|
||
# Add action to model. | ||
# | ||
# @example Add viewable action | ||
# class Post | ||
# act_as :viewable | ||
# ... | ||
# end | ||
# | ||
# @example Add sortable action | ||
# class User | ||
# act_as_sortable :email, :name | ||
# ... | ||
# end | ||
def act_as(*args) | ||
args.each do |action| | ||
action_class = action_module(action) | ||
unless action_class | ||
raise ActionModel::NameError, "Action #{action} is not found in [#{self.name.demodulize}::#{action.to_s.camelize}, Actions::#{action.to_s.camelize}]." | ||
end | ||
require 'active_support/core_ext/array/extract_options' | ||
require 'active_support/core_ext/string/inflections' | ||
require 'active_support/core_ext/object/blank' | ||
require 'active_support/concern' | ||
require 'actionmodel/context' | ||
|
||
actions[action.to_sym] ||= ActionModel::Context.new | ||
module ActionModel | ||
module Concern | ||
extend ActiveSupport::Concern | ||
|
||
include action_class | ||
# Return actions context hash for model instance. | ||
def actions | ||
self.class.actions | ||
end | ||
end | ||
|
||
# Action module for action name | ||
# | ||
# @param action [Symbol/String] action name | ||
def action_module(action) | ||
"#{self.name.demodulize}::#{action.to_s.camelize}".safe_constantize || | ||
"Actions::#{action.to_s.camelize}".safe_constantize | ||
end | ||
module ClassMethods | ||
|
||
# Add actions to model. | ||
# | ||
# Add single action to model | ||
# | ||
# class Post | ||
# act_as :searchable | ||
# ... | ||
# end | ||
# | ||
# Add actions to model | ||
# | ||
# class Post | ||
# act_as :searchable, :viewable | ||
# ... | ||
# end | ||
# | ||
# Actions can be added with options. Options applied for each action | ||
# | ||
# class Post | ||
# act_as :searchable, ignorecase: true | ||
# ... | ||
# end | ||
def act_as(*args) | ||
options = args.extract_options! | ||
|
||
args.each { |action_name| include_action action_name, options } | ||
end | ||
|
||
# Return actions context hash for model class. | ||
def actions | ||
@actions ||= {} | ||
end | ||
|
||
# Add action to model. | ||
# | ||
# | ||
# @example Add historyable action | ||
# class Rank | ||
# act_as_historyable :value, :boost, autofill: true | ||
# ... | ||
# end | ||
def method_missing(method, *args, &block) | ||
action = method[/^act_as_(.+)/, 1] | ||
if action.nil? | ||
super method, *args, &block | ||
else | ||
include_action *args.unshift(action) | ||
end | ||
end | ||
|
||
private | ||
|
||
# Add action to model. | ||
# | ||
# | ||
# @example Add historyable action | ||
# class Rank | ||
# act_as_historyable :value, :boost, autofill: true | ||
# ... | ||
# end | ||
def method_missing(method, *args, &block) | ||
action = method[/^act_as_(.+)/, 1] | ||
if action.nil? | ||
super(method, *args, &block) | ||
else | ||
options = args.extract_options! | ||
action = action.to_sym | ||
actions[action] = OpenStruct.new(args: args, options: options) | ||
|
||
act_as action | ||
# Include action into model | ||
def include_action(*args) | ||
return if args.empty? | ||
|
||
action_name = args.shift.to_sym | ||
|
||
action = action_module action_name | ||
unless action | ||
raise 'Action %1$s is not found in [%2$s::%3$s, Actions::%3$s].' % | ||
[ action_name, self.name.demodulize, action_name.to_s.camelize ] | ||
end | ||
|
||
find_or_create_action(action_name).update *args | ||
|
||
include action unless include? action | ||
end | ||
|
||
# Find or create action by action name | ||
def find_or_create_action(action_name) | ||
actions[action_name] ||= ActionModel::Context.new | ||
end | ||
|
||
# Return action module by action name | ||
def action_module(action) | ||
"#{self.name.demodulize}::#{action.to_s.camelize}".safe_constantize || | ||
"Actions::#{action.to_s.camelize}".safe_constantize | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,24 @@ | ||
module ActionModel | ||
class Context | ||
def options | ||
@options ||= {} | ||
end | ||
|
||
def fields | ||
@fields ||= {} | ||
end | ||
|
||
def update(*args) | ||
options = args.extract_options! | ||
|
||
if args.empty? | ||
self.options.merge! options | ||
else | ||
args.each do |field| | ||
fields[field.to_sym] ||= {} | ||
fields[field.to_sym].merge! options | ||
end | ||
end | ||
end | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
require 'spec_helper' | ||
|
||
describe ActionModel::Concern do | ||
let(:model) { Class.new { include ActionModel::Concern } } | ||
|
||
before do | ||
allow(model).to receive(:name).and_return 'Post' | ||
model.public_class_method :include_action, :find_or_create_action, :action_module | ||
end | ||
|
||
describe '.act_as' do | ||
it 'include action' do | ||
expect(model).to receive(:include_action).with :searchable, {} | ||
model.act_as :searchable | ||
end | ||
|
||
it 'include action with options' do | ||
expect(model).to receive(:include_action).with :searchable, { ignorecase: true } | ||
model.act_as :searchable, ignorecase: true | ||
end | ||
end | ||
|
||
describe '.include_action' do | ||
context 'action module exists' do | ||
let(:action) { double :action } | ||
|
||
before do | ||
allow(model).to receive(:action_module).and_return action | ||
allow(model).to receive(:include?).and_return false | ||
end | ||
|
||
it 'update action context' do | ||
allow(model).to receive(:include) | ||
expect_any_instance_of(ActionModel::Context).to receive(:update) | ||
model.include_action :searchable | ||
end | ||
|
||
it 'update action context with options' do | ||
allow(model).to receive(:include) | ||
expect_any_instance_of(ActionModel::Context).to receive(:update).with(ignorecase: true) | ||
model.include_action :searchable, ignorecase: true | ||
end | ||
|
||
it 'include action module' do | ||
expect(model).to receive(:include).with action | ||
model.include_action :searchable | ||
end | ||
end | ||
|
||
context 'action module does not exists' do | ||
before { allow(model).to receive(:action_module).and_return nil } | ||
|
||
it 'raise exception' do | ||
expect { model.include_action :unknownable }.to raise_error RuntimeError, 'Action unknownable is not found in [Post::Unknownable, Actions::Unknownable].' | ||
end | ||
end | ||
end | ||
|
||
describe '.action_module' do | ||
let(:action) { "#{model.name.demodulize}::Knownable" } | ||
|
||
context 'when a model action exists' do | ||
before { allow(ActiveSupport::Inflector).to receive(:safe_constantize).with(action).and_return action } | ||
|
||
subject { model.action_module :knownable } | ||
|
||
it { should eq action } | ||
end | ||
|
||
context 'when a global action exists' do | ||
let(:action) { 'Actions::Knownable' } | ||
|
||
before { allow(ActiveSupport::Inflector).to receive(:safe_constantize).and_return nil, action } | ||
|
||
subject { model.action_module :knownable } | ||
|
||
it { should eq action } | ||
end | ||
|
||
context 'when the action model is not exist' do | ||
before { allow(ActiveSupport::Inflector).to receive(:safe_constantize).and_return nil } | ||
|
||
subject { model.action_module(:unknownable) } | ||
|
||
it { should be_nil } | ||
end | ||
end | ||
|
||
describe '.method_missing' do | ||
context 'without action method' do | ||
it 'call class method' do | ||
expect(model).to receive(:class_method) | ||
model.class_method | ||
end | ||
end | ||
|
||
context 'with action method' do | ||
it 'config action' do | ||
expect(model).to receive(:include_action).with 'searchable', :name, ignorecase: true | ||
model.act_as_searchable :name, ignorecase: true | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters