A Railtie for loading autoloadable patches using Zeitwerk. This speeds up Rails bootup and reload time considerable, because other solutions out there load the patches and the associated classes on application bootup, and this gem defers loading the patches until the patched classes are autoloaded.
bundle add flickwerk
Include Flickwerk in your application.rb
or engine.rb
file, depending on whether you want to add patches to external libraries from an engine or from your host app.
# config/application.rb
# ...
class MyApplication < Rails::Application
include Flickwerk
# ...
end
Then, add patches to app/patches/models
or app/patches/controllers
that can be autoloaded.
# app/patches/models/user_patch.rb
module UserPatch
def name
"Engelbert Humperdinck"
end
User.prepend self
end
Now, all your users will have Engelbert Humperdinck's name.
You can use Ruby's prepended
hook in order to run class methods. That's useful for adding scopes or ActiveRecord relationships.
# app/patches/models/spree_order_patch.rb
module SpreeOrderPatch
def self.prepended(base)
base.has_many :goodies
end
Spree::Order.prepend self
end
If you application now has a class Goodie
with a foreign key to the spree_orders
table, you can check on those goodies using the familiar order.goodies
in your application.
You can also include Flickwerk in your engine.rb
file and add patches to your gem.
# lib/my_gem/engine.rb
class MyGem::Engine < Rails::Engine
include Flickwerk
# ...
end
It's advisable to namespace your patches so they do not conflict with patches added elsewhere:
# app/patches/modes/my_engine/spree_order_patch.rb
module MyEngine::SpreeOrderPatch
def self.prepended(base)
base.scope :with_goodies, -> { joins(:goodies).where.not(goodie_id: nil) }
end
Spree::Order.prepend self
end
Sometimes you want to modify the original result of a method dynamically rather than just overwrite it. Because your patches use Module.prepend
, this can be easily accomplished, even if you want to modify class methods:
# app/models/spree_order_patch.rb
module SpreeOrderPatch
module ClassMethods
def ransackable_associations
super + ["goodies"]
end
end
def self.prepended(base)
base.extend ClassMethods
end
def total
super - goodies.sum(&:amount)
end
Spree::Order.prepend self
end
Flickwerk will look for the string .prepend
in your patches in order to know which class should be patched. In some circumstances, the name of the class being patched is the result of a method call though, as in the case of patching Solidus' configurable user class. In these cases, you'll have to register the patch with Flickwerk manually:
# app/patches/models/my_gem/user_class_patch.rb
module MyGem::UserClassPatch
def name
"Dr. #{super}"
end
end
# lib/my_gem/engine.rb
require "flickwerk"
class MyGem::Engine < Rails::Engine
include Flickwerk
initializer "my_gem.patch_user_class", before: "flickwerk.add_patches" do
Flickwerk.patch(Spree.class_variable_get(:@@user_class), "MyGem::UserClassPatch")
end
end
Classes that are not autoloaded (very generally, that's everything in the lib
folder) cannot be loaded using the mechanism Flickwerk provides. Put any patches you have for these into your own lib
folder and require
them at your discretion:
# lib/my_gem/spree_patch.rb
module MyGem::SpreePatch
module ClassMethods
def river_name
"Havel"
end
end
def self.prepended(base)
base.extend ClassMethods
end
Spree.prepend self
end
Require these in a config.to_prepare block in your application
# lib/my_gem/engine.rb
class MyGem::Engine
# ...
initializer "load_lib_patches" do
config.to_prepare do
root.join("lib/patches").glob("**/*.rb").each do |f|
require f
end
end
end
# ...
end
Due to the Rails Engine documentation telling people to put their patches in either app/decorators
or app/overrides
, that's where a lot of patches currently live. Flickwerk provides a script to help you move your "decorators" or "overrides" to app/patches
and do the necessary changes in terms of file and constant names.
If your patches currently live in app/decorators
, run
bundle exec flickwerk patchify
Flickwerk will now move your decorators and rename them.
If your patches currently live in app/overrides
, use an argument:
bundle exec flickwerk patchify overrides
Now, run your tests and make sure your patches comply with Flickwerk's requirements.
Patches are often not the ideal solution. It's often preferable to fix any bugs upstream, or to use a specialized object just in those contexts where necessary. However, in some ecosystems, like the Spree/Solidus ones, core classes are extended in extension gems, and especially things like adding associations can be impossible on anything than the correct ActiveRecord class.
Choose your patches carefully, and always look for better solutions than a patch. Sometimes, they're a good, pragmatic solution though.
Because there are no monkeys involved in writing them - to my knowledge, this is all the work of human beings.
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/friendlycart/flickwerk. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the Flickwerk project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.