This is just a quick note to capture the broad strokes of the idea. Later, this could be wrapped in a gem and properly configured.
At work, I’m planning to start using private ActiveRecord models to help enforce boundaries between the parts of our rails application, as per this excellent article by Kelly Sutton.
I got thinking that it would be useful to enforce this rule programmatically, rather than adding it to the code review checklist. We already use rubocop to enforce some house rules, so why not write a new cop.
It looks something like this:
module RuboCop
module Cop
module Commsworld
class UnspecifiedModelPrivacy < Cop
MSG = "Specify an access modifier for namespaced class `%<class_name>s` with either " \
"`private_constant :%<class_name>s` or `public_constant :%<class_name>s`".freeze
def_node_matcher :private_constant_declaration?, <<~PATTERN
(send _ :private_constant _)
PATTERN
def_node_matcher :public_constant_declaration?, <<~PATTERN
(send _ :public_constant _)
PATTERN
def on_class(node)
return unless module_scope?(node)
klass_name = node.identifier.node_parts.last
node.parent.each_child_node do |child|
next unless private_constant_declaration?(child) || public_constant_declaration?(child)
return if klass_name == child.arguments.first.value
end
add_offense(node, message: message(node))
end
private
def message(node)
klass_name = node.identifier.node_parts.last
format(MSG, class_name: klass_name)
end
def module_scope?(node)
return unless node.parent
case node.parent.type
when :begin
module_scope?(node.parent)
when :module
true
end
end
end
end
end
end
Then, add a bit of config which enables it for app/models
:
HouseRules/PublicModel:
Enabled: Yes
Include:
- app/models/**/*.rb
A more complete write-up is coming once this is in a gem, and being used in anger.
Update: There is a gem! Have a look on github.