Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ActiveRecord and Sequel actual plugins #414

Merged
merged 7 commits into from
Aug 6, 2020
Merged

Conversation

shioyama
Copy link
Owner

@shioyama shioyama commented Jul 10, 2020

Currently on master, Mobility::Plugins::ActiveRecord, Mobility::Plugins::ActiveModel and Mobility::Plugins::Sequel are simply namespaces which hold modules called by Mobility::Plugins::Dirty depending on what gems are loaded.

This is really ugly, because it means that code that should be generic has to know every possible ORM variant.

With this PR, we use inversion of control to flip this situation around so that active_record and sequel are real plugins which themselves call active_record_dirty or sequel_dirty, active_record_cache or sequel_cache, etc. These latter plugins depend are triggered by inclusion of the generic ones (dirty, cache).

So basically, if you have:

class TranslatedAttributes < Mobility::Attributes
  plugin :active_record
  plugin :dirty
end

then the active_record plugin includes active_record_dirty, which itself depends on dirty (with include: false). This way, the AR specific dirty code is only triggered if you actually enable dirty. Same for Sequel.

The other thing that happens here is that we assume that if you have plugin :active_record, the model you're translating must be an ActiveRecord::Base model. Previously, the Dirty plugin would trigger on the included hook and check which class it was included into, but this meant that the model class would get more modules than necessary.

Now, everything is explicit and you must declare plugin :active_record, and when you do, Mobility expects translated classes to be ActiveRecord classes, etc. If (in that 0.001% of cases) you are using Mobility with both ActiveRecord and Sequel, you'd just have to create two module builders, one for each:

class ActiveRecordTranslatedAttributes < Mobility::Attributes
  plugin :active_record
  # ...
end

class SequelTranslatedAttributes < Mobility::Attributes
  plugin :sequel
  # ...
end

class ActiveRecordModel < ActiveRecord::Base
  include ActiveRecordTranslatedAttributes.new(:title)
end

class SequelModel < Sequel::Model
  include SequelTranslatedAttributes.new(:title)
end

So since everything is now more modular, this is still entirely possible.

The nice thing here is that all the ugly code which was implicitly trying to figure out what gems are loaded (to automatically switch between ORM classes) is gone. I think explicitness here is much clearer and safer.

Same goes for backends: the active_record plugin loads an active_record_backend plugin which overrides load_backend to first search for a backend under Mobility::Backends::ActiveRecord (same for sequel_backend). This again means a lot of magic becomes less magical, which I think is good.


Update: I've also extracted UniquenessValidation into its own plugin, which is automatically included when you call plugin :active_record.

@shioyama shioyama force-pushed the activerecord_plugin branch 2 times, most recently from c6daf66 to 7d07303 Compare July 10, 2020 05:35
@shioyama shioyama force-pushed the activerecord_plugin branch from 7d07303 to e6aa7db Compare July 26, 2020 05:37
raise unless e.message =~ /sequel/
Loaded::Sequel = false
end

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will be so happy when this is all gone.

@shioyama shioyama force-pushed the activerecord_plugin branch 17 times, most recently from 11bc97e to efabb23 Compare August 2, 2020 03:34
@shioyama shioyama force-pushed the activerecord_plugin branch 4 times, most recently from 8a9aa5a to 69a9bdd Compare August 6, 2020 02:28
Previously, plugins like Dirty would branch out to different
implementations from the includes hook depending on the including class,
since ActiveRecord and Sequel must be handled differently.

This meant that code that should be generic has to know about every
possible ORM-specific variant of itself.

Here we use Inversion of Control by making the reasonable assumption
that an Attributes module is configured for a particular ORM model. We
can then flip this situation around so that active_record and sequel are
real plugins which themselves call ORM-specific implementations of
generic plugins.

So, for example, if you have:

```
class TranslatedAttributes < Mobility::Attributes
  plugin :active_record
  plugin :dirty
end
```

Then the active_record plugin includes active_record_dirty, which itself
depends on dirty. This way, the AR specific dirty code is only triggered
if you actually enable dirty. Same for Sequel.

With this change, basically everything under lib/mobility/plugins is
actually a plugin, managing its own dependencies.
@shioyama shioyama force-pushed the activerecord_plugin branch from 69a9bdd to 4a84940 Compare August 6, 2020 02:37
@shioyama shioyama force-pushed the activerecord_plugin branch from a90e71a to 9a8d114 Compare August 6, 2020 03:04
@shioyama shioyama merged commit 9a8d114 into master Aug 6, 2020
@github-pages github-pages bot temporarily deployed to github-pages August 6, 2020 03:51 Inactive
@shioyama shioyama deleted the activerecord_plugin branch August 6, 2020 04:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant