Rails Association belongs_to marking as optional dynamically in class and mixin - ruby

I have came across a scenario in an existing code base.
Im upgrading Rails to 5.1 from 5.0 and we now need to define relationships as optional where its not required.
I have came across a situation where we have a class and a mixin that are causing conflict.
Is there a way to define the relationship with the :user so that it will meet requirements in both scenarios.
In the Model, a user is not required, but within the mixin, a users presence is validated, causing the conflict.
As the Mixin is included, writing a proc to evaluate whether to mark the relationship as optional by using a function/proc results in the class method being used at runtime. If you prepend the mixin instead, it will default to the method in the mixin, meaning either way they conflict.
class Foo < ActiveRecord::Base
belongs_to :user, optional: true
end
module Mixins
module Foo
extend ActiveSupport::Concern
validates :user, presence: true, if: :user_is_required?
private
def user_is_required?
<!-- LOGIC HERE -->
end
end
end
At an engine level.. the mixin is included as so:
::Foo.include(Mixins::Foo)

Related

Best way to validate an external gem's ActiveRecord model

What is the best way to add validations to an external Gem's activerecord model?
module Library
class Model < ActiveRecord::Base
validates :field_active, inclusion: { in: [true, false] }
end
end
I would like to add another validates to it e.g.
validates :me_too, presence: true
But since it's an external gem, it's not really possible to just add it below validates :field_active.
I would like to know what the best practice way of doing this is.
Is class_eval the best way or is that frowned upon?
Ruby has so-called "open classes". You can open any class (at any time) and add more methods to it. This should work:
module Library
class Model
validates :me_too, presence: true
end
end
It opens already defined Library::Model and just adds one more line of code to what's already there (not replacing/removing that existing code).
Note that this depends on Library::Model being already loaded (this is not guaranteed). This should be a safer alternative:
Library::Model.instance_eval do
validates :me_too, presence: true
end
It will trigger loading of Library::Model if it's not already loaded.

Adding ActiveRecord validations to PaperTrail's Version model?

I'm trying to add a validation to PaperTrail::Version which will prevent sensitive data from being stored in the versions table. The idea being you'll get lots of obvious errors if you forget to sanitize your has_paper_trail call within your model.
If I add a custom validator in config/initializers/paper_trail it works ... for a while. Then PaperTrail starts acting with its default behavior and my methods are undefined.
Example Code:
PaperTrail::Rails::Engine.eager_load!
module PaperTrail
class Version
# Ensure no sensitive values end up in the versions table
validate :prohibited_attributes
...
Try a custom version class. See documentation section 6.a. Custom Version Classes.
6.a. Custom Version Classes
You can specify custom version subclasses with the :class_name
option:
class PostVersion < PaperTrail::Version
# custom behaviour, e.g:
self.table_name = :post_versions
end
class Post < ActiveRecord::Base
has_paper_trail :class_name => 'PostVersion'
end
Using PaperTrail::Rails::Engine.eager_load! was a good idea. Not sure why that didn't work for you. Hopefully this is a workaround.

How do I require classes with a circular dependency? [duplicate]

This question already has answers here:
Circular Dependencies in Ruby
(3 answers)
Closed 9 years ago.
I've been spoiled by Rail's autoloading of missing constants. In Ruby, if I have two classes, one nested inside the other but in different files, how do I require them since both depend on each other (circular dependency).
# user.rb
class User < ActiveRecord::Base
serialize :preferences, User::Preferences
end
# user/preferences.rb
class User::Preferences
end
# user_spec.rb
require 'user'
require 'user/preferences'
Note: I have not required the Rails environment.
If I try and load User first, the code fails because it does not know about User::Preferences yet. If I load "user/preferences" first, it fails when it loads User because the existing User class does not subclass ActiveRecord.
I have a suspicion I need to remove the circular dependency or, if possible, make serialize lazy load the class by passing a string 'User::Preferences' which is turned in to a constant when needed.
One hack I have is to create an empty User class inheriting from ActiveRecord::Base in user/preferences.rb:
class User < ActiveRecord::Base; end
class User::Preferences
end
Rather than wire knowledge of User's implementation into User::Preferences you could put the stub declaration in a common base, like so:
# user_base.rb
class User < ActiveRecord::Base; end
# user.rb
require 'user_base'
require 'user/preferences'
class User
serialize :preferences, User::Preferences
...
end
# user/preferences.rb
require 'user_base'
class User::Preferences
end
Alternatively, you could move User::Preferences into an independent module namespace such as ModelHelper::User::Preferences. I think I prefer this solution. The fact that you have a circular dependency is a code smell and the only thing causing it is the reuse of User class as a namespace container for User::Preferences.

Rails equivalent of ApplicationController for models

I understand that application_controller.rb is the place to put all the methods, etc that you would like made available in all your controllers since they all inherit from this class. Great.
But what is the equivalent for Models? In other words, I want a place where I can create a couple of super classes that my models will inherit from.
For example, I have a method that searches different tables for entries in all CAPS via REGEXP in Mysql. I'd like to be able to create the method only once and call it for different tables/models.
What is the Rails way of doing this?
I thought I could create a class that would inherit from ActiveRecord::Base (as all models do) , put the methods in there and then inherit all my models from that class. But thought there would surely be a better way to do it.
Thanks.
Edit
Per Semyon's answer I'm editing the post to show the routes I am using. It works now:
# models/dvd.rb
require 'ModelFunctions'
class Dvd < ActiveRecord::Base
extend ModelFunctions
...
end
# lib/ModelFunctions.rb
module ModelFunctions
def detect_uppercase(object)
case object
...
where("(#{field} COLLATE utf8_bin) REGEXP '^[\w[:upper:]]{5,}' ").not_locked.reorder("LENGTH(#{field}), #{table}.#{field} ASC")
end
end
In config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
Take a look at mixins, for example here:
http://ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
In a Rails app you could create a module in the lib directory that defines your methods and then include it in your models.
EDIT: To be specific for your example, you're trying to define a class method. You can do this in a mixin like this:
module Common
module ClassMethods
def detect_uppercase(object)
case object
when 'dvd'
field = 'title'
...
end
where("(#{field} COLLATE utf8_bin) REGEXP '^[\w[:upper:]] {5,}').not_locked.reorder('LENGTH(title), title ASC')"
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
Now when you include Common in your model, that model's class will be extended to include the new class methods, and you should be able to call Dvd.detect_uppercase.
Put the reusable method in some module next to your Dvd class. You can move it in a separate file later.
# app/models/dvd.rb
module CaseInsensitiveSearch
def case_insensitive_search(field, value)
# searching field for value goes here
end
end
class Dvd
end
After extending a class with the module you can use case_insensitive_search on the class. Including the module will make case_insensitive_search an instance method which is not what you want.
class Dvd
extend CaseInsensitiveSearch
end
Dvd.case_insensitive_search("title", "foo")
And of course you can use it inside Dvd class.
class Dvd
def self.search(query)
case_insensitive_search("title", query)
end
end
Dvd.search("foo")
Now when you made sure it works, you will probably want to move it in a separate file and use it across multiple classes. Place it in lib/case_insensitive_search.rb and make sure you have this line in config/application.rb:
config.autoload_paths += %W(#{config.root}/lib)
Now you can require it anywhere you want to use it:
require 'case_insensitive_search'
class Dvd
extend CaseInsensitiveSearch
end
The last thing I'd like to suggest. Create multiple modules with meaningful names. So instead of CommonModel have CaseInsensitiveSearch and so on.

Ruby "Base" classes

It seems commonplace to name classes "Base" in Ruby. I'm not sure why, nor how I feel about it.
Consider, for example, ActiveRecord. ActiveRecord is a module that contains a number of classes such as Observer and Migration, as well as a class called Base. What's the benefit of this, as opposed to having an ActiveRecord class that contains Observer and Migration?
class ActiveRecord
class Observer
[...]
end
class Migration
[...]
end
end
vs
module ActiveRecord
class Base
[...]
end
class Observer
[...]
end
class Migration
[...]
end
end
The Base class is commonly used to identify an abstract class, intended to be extended and implemented in a concrete class by the developer.
For instance, ActiveRecord::Base is the abstract class for any Active Record model in a Rails project. A model looks like
class User < ActiveRecord::Base
end
Likewise, Observer defines its own Observer::Base and Action Controller defines ActionController::Base which, in a Rails project, is immediately implemented by ApplicationController::Base.
Ruby doesn't provide and language-level keyword or syntax to define abstract classes. Technically speaking, ActiveRecord::Base it's not a real abstract class, but it's a kind of convention to use Base for this pattern.

Resources