How to add custom model modifier like in most gems? - ruby-on-rails

I want to DRY some model functionality like in a lot of gems.
For example in gem acts-as-taggable-on we should just add acts_as_taggable to any model to enable tagging on it.
How I can add my own include_god_mode model modifier into my rails project?

So I found out that it is implemented by simple class extending and mixing.
For example in the acts-as-taggable-on gem there are a lot of modules which included into ActiveRecord::Base so acts_as_taggable in a model is just a syntax sugar.
Here is acts-as-taggable-on sources with including:
if defined?(ActiveRecord::Base)
ActiveRecord::Base.extend ActsAsTaggableOn::Taggable
ActiveRecord::Base.send :include, ActsAsTaggableOn::Tagger
end
if defined?(ActionView::Base)
ActionView::Base.send :include, ActsAsTaggableOn::TagsHelper
end
So if I want to add such sugar to my model I need to include a method into ActiveRecord::Base (or similar superclass in case of ActiveRecord ORM) that will include module with my stuff.

Related

Rails Active Storage and Acts as List

I've been working on a way to add acts_as_list to ActiveStorage::Attachment using a concern. I've been working on this for two days and can't seem to find a way to add the line acts_as_list to the model. I tried to load with an initializer. Any thoughts?
Here's the initializer code:
Rails.configuration.to_prepare do
class ActiveStorage::Attachment
include ActiveStorageActsAsList
end
end
Concern
module ActiveStorageActsAsList
extend ActiveSupport::Concern
included do
acts_as_list
end
end

Override gem behaviour

I'm using the acts_as_bookable gem for some basic reservation/booking stuff in a Rails app, and I need to add an additional validation to the Booking model that the gem creates.
What I mean by that is, inside the gem, located at lib/acts_as_bookable/booking.rb is the following module/class:
module ActsAsBookable
class Booking < ::ActiveRecord::Base
self.table_name = 'acts_as_bookable_bookings'
belongs_to :bookable, polymorphic: true
belongs_to :booker, polymorphic: true
validates_presence_of :bookable
validates_presence_of :booker
validate :bookable_must_be_bookable,
:booker_must_be_booker
# A bunch of other stuff
end
end
Which is fine. However, I want to add an additional piece of logic that stops a booker from booking the same instance of a bookable. Basically, a new validator.
I thought I could just add a file in my /models directory called acts_as_bookable.rb and just modify the class like this:
module ActsAsBookable
class Booking
validates_uniqueness_of :booker, scope: [:time, :bookable]
end
end
But this doesn't work. I could modify the gem itself (I've already forked it to bring a few dependencies up to date, since it's a pretty old gem) but that doesn't feel like the right solution. This is logic specific to this app's implementation, and so my gut feeling is that it belongs in an override inside this specific project, not the base gem.
What am I doing wrong here? And is there a better/alternative approach that would be more suitable?
A clean way to create monkeypatches/augmentations to objects outside of your control is to create a seperate module:
module BookingMonkeyPatch
extend ActiveSupport::Concern
included do
validates_uniqueness_of :booker, scope: [:time, :bookable]
end
end
This lets you test the monkeypatch seperately - and you can "turn the monkeypatch on" by including the module:
ActsAsBookable::Booking.include(BookingMonkeyPatch)
This can be done in an initializer or anywhere else in the lifecycle.
Altough if bookable is a polymorpic assocation you need to use:
validates_uniqueness_of :booker_id, scope: [:time, :bookable_id, :bookable_type]
The uniqueness validation does not work correctly when just passed the name of an assocation as it creates a query based on database columns. This is an example of a leaky abstraction.
See:
Justin Weiss - 3 Ways to Monkey-Patch Without Making a Mess

How to get all Users using a Plugin in Redmine/Ruby on Rails?

I'm coding a Plugin for Redmine in Ruby on Rails atm.
I need to get all "Users" to link them to a "Skill". So I need
all users to make a relationship to my skills. As it is a plugin, I don't want to write in the
main users model in Redmine. So, I kinda want to extend or something the original user model.
Anyone has a clue how I can solve this?
If you want to add logic to an already existing class (like adding new methods, relationships, validations, etc..), you can do it with Ruby Module#class_eval:
User.class_eval do
# Inside this block we add the new logic that we want to add to the User class
def new_method
end
end
To patch models in Redmine I used to use this approach:
# plugins/your_plugin_name/lib/your_plugin_name/user_path.rb
module YourPluginName
module UserPatch
extend ActiveSupport::Concern
included do
has_many :skills
end
def some_new_method
end
end
end
User.include YourPluginName::UserPatch

Rails 4: ActiveRecord-style I18n on a non-ActiveRecord object?

I have a Plain Old Ruby Class (PORO) that behaves a lot like an ActiveRecord model, although it is not persisted to the database.
To make the internationalization(I18n) as painlessly as possible, I would like to also use the SomeModel.model_name.human and SomeModel.human_attribute_name(:attribute) methods on this PORO.
What module do I need to include to include above methods on my PORO?
Extend your class with the ActiveModel::Translation module:
class Widget
extend ActiveModel::Translation
end
Widget.model_name.human
=> "Widget"
Widget.human_attribute_name :my_attribute
=> "My attribute"

May I include in ActiveRecord model a new relation from my plugin?

I'm creating a plugin for Redmine. I want to use the core Issue model, but I want to include in it a relation with my model, which created in the plugin structure. How can I patch the Issue model for including a new relationship in my plugin?
You can create a decorator in your main project to decorate the Issue model:
# app/decorators/issue_decorator.rb
Redmine::Issue.class_eval do
has_many :blurps # or whatever your model is called
end
example with Project model
require 'project'
module ProjectPatch
def self.included(base)
base.has_one :project_settings
end
end
Project.send :include, ProjectPatch

Resources