Rails model without database - ruby-on-rails

I want to create a Rails (2.1 and 2.2) model with ActiveRecord validations, but without a database table. What is the most widely used approach? I've found some plugins that claim to offer this functionality, but many of them don't appear to be widely used or maintained. What does the community recommend I do? Right now I am leaning toward coming up with my own solution based on this blog post.

There is a better way to do this in Rails 3: http://railscasts.com/episodes/219-active-model

This is an approach I have used in the past:
In app/models/tableless.rb
class Tableless < ActiveRecord::Base
def self.columns
#columns ||= [];
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default,
sql_type.to_s, null)
end
# Override the save method to prevent exceptions.
def save(validate = true)
validate ? valid? : true
end
end
In app/models/foo.rb
class Foo < Tableless
column :bar, :string
validates_presence_of :bar
end
In script/console
Loading development environment (Rails 2.2.2)
>> foo = Foo.new
=> #<Foo bar: nil>
>> foo.valid?
=> false
>> foo.errors
=> #<ActiveRecord::Errors:0x235b270 #errors={"bar"=>["can't be blank"]}, #base=#<Foo bar: nil>>

There is easier way now:
class Model
include ActiveModel::Model
attr_accessor :var
validates :var, presence: true
end
ActiveModel::Model code:
module ActiveModel
module Model
def self.included(base)
base.class_eval do
extend ActiveModel::Naming
extend ActiveModel::Translation
include ActiveModel::Validations
include ActiveModel::Conversion
end
end
def initialize(params={})
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
end
def persisted?
false
end
end
end
http://api.rubyonrails.org/classes/ActiveModel/Model.html

I think the blog post you are linking is the best way to go. I would only suggest moving the stubbed out methods into a module not to pollute your code.

just create a new file ending in ".rb" following the conventions you're used to (singular for file name and class name, underscored for file name, camel case for class name) on your "models/" directory. The key here is to not inherit your model from ActiveRecord (because it is AR that gives you the database functionality).
e.g.: for a new model for cars, create a file called "car.rb" in your models/ directory and inside your model:
class Car
# here goes all your model's stuff
end
edit: btw, if you want attributes on your class, you can use here everything you use on ruby, just add a couple lines using "attr_accessor":
class Car
attr_accessor :wheels # this will create for you the reader and writer for this attribute
attr_accessor :doors # ya, this will do the same
# here goes all your model's stuff
end
edit #2: after reading Mike's comment, I'd tell you to go his way if you want all of the ActiveRecord's functionality but no table on the database. If you just want an ordinary Ruby class, maybe you'll find this solution better ;)

For the sake of completeness:
Rails now (at V5) has a handy module you can include:
include ActiveModel::Model
This allows you to initialise with a hash, and use validations amongst other things.
Full documentation is here.

There's a screencast about non-Active Record model, made up by Ryan Bates. A good place to start from.
Just in case you did not already watch it.

I have built a quick Mixin to handle this, as per John Topley's suggestion.
http://github.com/willrjmarshall/Tableless

What about marking the class as abstract?
class Car < ActiveRecord::Base
self.abstract = true
end
this will tell rails that the Car class has no corresponding table.
[edit]
this won't really help you if you'll need to do something like:
my_car = Car.new

Use the Validatable gem. As you say, there are AR-based solutions, but they tend to be brittle.
http://validatable.rubyforge.org/

Anybody has ever tried to include ActiveRecord::Validations and ActiveRecord::Validations::ClassMethods in a non-Active Record class and see what happens when trying to setup validators ?
I'm sure there are plenty of dependencies between the validation framework and ActiveRecord itself. But you may succeed in getting rid of those dependencies by forking your own validation framework from the AR validation framework.
Just an idea.
Update: oopps, this is more or less what's suggested in the post linked with your question. Sorry for the disturbance.

Do like Tiago Pinto said and just don't have your model inherit from ActiveRecord::Base. It'll just be a regular Ruby class that you stick in a file in your app/models/ directory. If none of your models have tables and you're not using a database or ActiveRecord at all in your app, be sure to modify your environment.rb file to have the following line:
config.frameworks -= [:active_record]
This should be within the Rails::Initializer.run do |config| block.

You ought to checkout the PassiveRecord plugin. It gives you an ActiveRecord-like interface for non-database models. It's simple, and less hassle than fighting ActiveRecord.
We're using PassiveRecord in combination with the Validatable gem to get the OP's desired behaviour.

Related

Rails Active Storage - Keep Existing Files / Uploads?

I have a Rails model with:
has_many_attached :files
When uploading via Active Storage by default if you upload new files it deletes all the existing uploads and replaces them with the new ones.
I have a controller hack from this which is less than desirable for many reasons:
What is the correct way to update images with has_many_attached in Rails 6
Is there a way to configure Active Storage to keep the existing ones?
Looks like there is a configuration that does exactly that
config.active_storage.replace_on_assign_to_many = false
Unfortunately it is deprecated according to current rails source code and it will be removed in Rails 7.1
config.active_storage.replace_on_assign_to_many is deprecated and will be removed in Rails 7.1. Make sure that your code works well with config.active_storage.replace_on_assign_to_many set to true before upgrading.
To append new attachables to the Active Storage association, prefer using attach.
Using association setter would result in purging the existing attached attachments and replacing them with new ones.
It looks like explicite usage of attach will be the only way forward.
So one way is to set everything in the controller:
def update
...
if model.update(model_params)
model.files.attach(params[:model][:files]) if params.dig(:model, :files).present?
else
...
end
end
If you don't like to have this code in controller. You can for example override default setter for the model eg like this:
class Model < ApplicationModel
has_many_attached :files
def files=(attachables)
files.attach(attachables)
end
end
Not sure if I'd suggest this solution. I'd prefer to add new method just for appending files:
class Model < ApplicationModel
has_many_attached :files
def append_files=(attachables)
files.attach(attachables)
end
end
and in your form use
<%= f.file_field :append_files %>
It might need also a reader in the model and probably a better name, but it should demonstrate the concept.
The solution suggested for overwriting the writer by #edariedl DOES NOT WORK because it causes a stack level too deep
1st solution
Based on ActiveStorage source code at this line
You can override the writer for the has_many_attached like so:
class Model < ApplicationModel
has_many_attached :files
def files=(attachables)
attachables = Array(attachables).compact_blank
if attachables.any?
attachment_changes["files"] =
ActiveStorage::Attached::Changes::CreateMany.new("files", self, files.blobs + attachables)
end
end
end
Refactor / 2nd solution
You can create a model concern that will encapsulate all this logic and make it a bit more dynamic, by allowing you to specify the has_many_attached fields for which you want the old behaviour, while still maintaining the new behaviour for newer has_many_attached fields, should you add any after you enable the new behaviour.
in app/models/concerns/append_to_has_many_attached.rb
module AppendToHasManyAttached
def self.[](fields)
Module.new do
extend ActiveSupport::Concern
fields = Array(fields).compact_blank # will always return an array ( worst case is an empty array)
fields.each do |field|
field = field.to_s # We need the string version
define_method :"#{field}=" do |attachables|
attachables = Array(attachables).compact_blank
if attachables.any?
attachment_changes[field] =
ActiveStorage::Attached::Changes::CreateMany.new(field, self, public_send(field).public_send(:blobs) + attachables)
end
end
end
end
end
end
and in your model :
class Model < ApplicationModel
include AppendToHasManyAttached['files'] # you can include it before or after, order does not matter, explanation below
has_many_attached :files
end
NOTE: It does not matter if you prepend or include the module because the methods generated by ActiveStorage are added inside this generated module which is called very early when you inherit from ActiveRecord::Base here
==> So your writer will always take precedence.
Alternative/Last solution:
If you want something even more dynamic and robust, you can still create a model concern, but instead you loop inside the attachment_reflections of your model like so :
reflection_names = Model.reflect_on_all_attachments.filter { _1.macro == :has_many_attached }.map { _1.name.to_s } # we filter to exclude `has_one_attached` fields
# => returns ['files']
reflection_names.each do |name|
define_method :"#{name}=" do |attachables|
# ....
end
end
However I believe for this to work, you need to include this module after all the calls to your has_many_attached otherwise it won't work because the reflections array won't be fully populated ( each call to has_many_attached appends to that array)

How can I store a regex expression string in a helper method to use to validate several different fields?

I have several different fields that store different phone numbers in my rails app as a string. I can use the built-in rails format validator with a regex expression. Is there a way to place this expression in a helper method for all my phone number validations instead of having to open each model file and paste the expression string.
You can require any file from application.rb:
# application.rb
require Rails.root.join "lib", "regexes.rb"
# lib/regexes.rb
PHONE_NUMBER_REGEX = /your regex/
Then you simply use the constant wherever needed
You can alternatively make use of the built in autoload functionality of Rails, for example with the concern approach the other commenter laid out - the concern file is autoloaded by default, as are models, controllers, etc
Loading custom files instead of using the Rails' defaults might not seem idiomatic or the "rails way". However, I do think it's important to understand that you can load any files you want. Some people autoload the entire lib/ folder and subfolders (see Auto-loading lib files in Rails 4)
Another alternative is to place your code somewhere in the config/initializers folder, these files are automatically loaded at startup and you can define shared classes/modules/constants there
Add a custom validator app/validators/phone_validator.rb
class PhoneValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.to_s =~ /YOUR_REGEX_HERE/
record.errors.add attribute, 'must be a valid phone number'
end
end
end
Then in your models
class MyModel < ApplicationRecord
#phone: true tells it to use the PhoneValidator defined above
validates :phone_number, presence: true, phone: true
end
One way to do this is to create a concern that will be included in each model that has a phone number. In models/concerns, create a new file called something like phonable.rb.
# models/concerns/phonable.rb
module Phonable
extend ActiveSupport::Concern
VALID_PHONE_REGEX = /\A\+?\d+\z/ # Use your regex here
end
Now include the concern like this:
# models/my_model.rb
class MyModel < ApplicationRecord
include Phonable
validates :phone, format: {with: VALID_PHONE_REGEX}
...
end
Now that you have a Phonable concern, you can put any other phone-number-related logic here as well, such as parsing and the like. The advantage of this approach is that all your logic related to phone numbers will be available for use in the models that need it, and none of that logic will be available in models that don't need it.
You can put it in ApplicationRecord (application_record.rb)
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
VALID_PHONE_NUMBER_REGEX = /\d{10}/ # your regex here
end
And then you can use it in any model that inherits fro ApplicationRecord

How do I handle migration of ActiveRecord::Base which implemented a tableless model

We had the following class to process SOAP responses from external API(s) which worked fine in ruby 1.8.7, but it is looking for a table with these columns post migration (which has never been there) to ruby 1.9.2/rails 3.1, How do I handle this migratation?
class SoapResponse < ActiveRecord::Base
def self.columns
#columns ||= [];
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(
name.to_s, default, sql_type.to_s, null)
end
def save(validate = true)
validate ? valid? : true
end
column :soap_payload, :text
serialize :soap_payload
end
You don't (have any migrations for it).
You don't have migrations and you don't inherit from ActiveRecord::Base as that is the database ORM component.
If you use a generator to create the model use --skip-migration to avoid generating the database migration file.
You can still get validations and conversions though, e.g.
class SoapResponse
include ActiveModel::Validations
include ActiveModel::Conversion
If you want some setup data (i.e. Constants, given there is no db! ) you can just define them here (Constants start with uppercase).
I guess you want the serialization capability of ActiveRecord::Base. That seems to be the only thing this class is using it for. If so, try calling this in your class definition:
self.abstract_class = true
Or you could try using ActiveModel::Serialization.
The pattern in your code looks like what's suggested in this answer for table-less AR models in Rails 2.

How to remove validation using instance_eval clause in Rails?

I would like to enhance existing class using instance_eval. There original definition contains validation, which require presence of certain fields, ie:
class Dummy < ActiveRecord::Base
validates :field, :presence => true
end
Now I want to change that to optional using instance_eval (or any other method, really):
Dummy.instance_eval do
...
end
What would be the proper syntax to remove the validation, so the field is optional. I would rather do this directly on the model layer, instead doing weird hacks in controllers or views. The use of instance_eval is not really required, but as far as I know, this is generally the best way to enhance classes in Rails.
Edit #1
In general - the original class is part of the gem and I don't want to fork it, nor tie to specific release. The general cause is not really important. Simply editing the original model has far worse consequences than monkey patching.
I found a solution, not sure how solid it is, but it works well in my case. #aVenger was actually close with his answer. It's just that the _validators accessor contains only information used for reflection, but not the actual validator callbacks! They are contained in the _validate_callbacks accessor, not to be confused with _validations_callbacks.
Dummy.class_eval do
_validators.reject!{ |key, _| key == :field }
_validate_callbacks.reject! do |callback|
callback.raw_filter.attributes == [:field]
end
end
This will remove all validators for :field. If you want to be more precise, you can reject the specific validator for _validators which is the same as the raw_filter accessor of validate callbacks.
I think this is the most actual solution at this moment (I'm using rails 4.1.6):
# Common ninja
class Ninja < ActiveRecord::Base
validates :name, :martial_art, presence: true
end
# Wow! He has no martial skills
Ninja.class_eval do
_validators[:martial_art]
.find { |v| v.is_a? ActiveRecord::Validations::PresenceValidator }
.attributes
.delete(:martial_art)
end
Easest way to remove all validations:
clear_validators!
As I was trying to do this to remove the phone validation from the spree Address model, below is the code I got to work. I added the type check for callback.raw_filter because I only wanted to remove the presence validator on the phone field. I also had to add it because it would fail when trying to run against one of the other validators specified in the Spree::Address model that did not have an 'attributes' key for callback.raw_filter, thus an exception was thrown.
Spree::Address.class_eval do
# Remove the requirement on :phone being present.
_validators.reject!{ |key, _| key == :phone }
_validate_callbacks.each do |callback|
callback.raw_filter.attributes.delete :phone if callback.raw_filter.is_a?(ActiveModel::Validations::PresenceValidator)
end
end
I had a similar problem and was able to get past it using:
class MyModel << Dummy
# erase the validations defined in the plugin/gem because they interfere with our own
Dummy.reset_callbacks(:validate)
...
end
This is under Rails 3.0. The caveat: It does remove ALL validations, so if there are others you want to keep you could try Dummy.skip_callback(...), but I could not figure out the right incantation of arguments to make that work.
One solution is to extend validates :
#no need of instance_eval just open the class
class Dummy < ActiveRecord::Base
#validates :field, :presence => true
def self.validates(*attributes)
if attributes.first == :field #=> add condition on option if necessary
return # don't validate
else
super(*attributes) #let normal behavior take over
end
end
end
And no that's not monkey-patching but extending or decorating a behavior. Rails 3.1 is built on the idea of "multi- inheritance" with module inclusion, specifically to allow this kind agility.
update #2
One caveat is you must load the class with the redefined validates method before the gem containing the call to validates. To do so, require the file in config/application.rb after require "rails/all" as suggested in the railsguides. Something like that :
require File.expand_path('../boot', __FILE__)
require 'rails/all' # this where rails (including active_record) is loaded
require File.expand_path('../dummy' __FILE__) #or wherever you want it
#this is where the gems are loaded...
# the most important is that active_record is loaded before dummy but...
# not after the gem containing the call to validate :field
if defined?(Bundler)
Bundler.require *Rails.groups(:assets => %w(development test))
end
Hope it works now!
Answer by aVenger has problems when you declare validations of more than one attribute in a line:
validates :name, :message, :presence => true
That's because this line creates a raw_filter with more than one attribute in attributes filter:
Model.send(:_validate_callbacks)
=> [#<ActiveSupport::Callbacks::Callback:0xa350da4 #klass=Model(...), ... , #raw_filter=#<ActiveModel::Validations::PresenceValidator:0x9da7470 #attributes=[:name, :message], #options={}>, #filter="_callback_before_75", #compiled_options="true", #callback_id=76>]
We have to delete the desired attribute from that array and reject the callbacks without attributes
Dummy.class_eval do
_validators.reject!{ |key, _| key == :field }
_validate_callbacks.each do |callback|
callback.raw_filter.attributes.delete :field
end
_validate_callbacks.reject! do |callback|
callback.raw_filter.attributes.empty? ||
callback.raw_filter.attributes == [:field]
end
end
I have this working on a Rails 3.2.11 app.
For rails 4.2 (~ 5.0) it can be used the following module with a method:
module ValidationCancel
def cancel_validates *attributes
attributes.select {|v| Symbol === v }.each do |attr|
self._validators.delete( attr )
self._validate_callbacks.select do |callback|
callback.raw_filter.try( :attributes ) == [ attr ] ;end
.each do |vc|
self._validate_callbacks.delete( vc ) ;end ;end ;end ;end
Note: Since the filtern can be a symbol of an association, or a specific validator, so we have to use #try.
Then we can use rails-friendly form in a class declaration:
class Dummy
extend ValidationCancel
cancel_validates :field ;end
Note: since removal of the validator is affecting to the whole class and its descendants globally, it is not recommended to use it to remove validations in such way, instead add if clause for the specific rule as follows:
module ValidationCancel
def cancel_validates *attributes
this = self
attributes.select {|v| Symbol === v }.each do |attr|
self._validate_callbacks.select do |callback|
callback.raw_filter.try( :attributes ) == [ attr ] ;end
.each do |vc|
ifs = vc.instance_variable_get( :#if )
ifs << proc { ! self.is_a?( this ) } ;end ;end ;end ;end
This restricts execution of the validation callback for the specified class and its descendants.
If you doesn't want to make any changes in Parent class then first clear all validations in child class and copy all required validation from parent class to child class
class Dummy < ActiveRecord::Base
validates :property, presence: true
validates :value, length: { maximum: 255 }
end
And override it in child class
Dummy.class_eval do
clear_validators!
validates :property, presence: true
end
If you really want to do this then here would be a good place to start digging: https://github.com/rails/rails/blob/ed7614aa7de2eaeba16c9af11cf09b4fd7ed6819/activemodel/lib/active_model/validations/validates.rb#L82
However, to be honest, inside of ActiveModel is not where I'd be poking with a stick.
If you can edit the constraint on the original model to put an :if => :some_function on it, you can easily change the behavior of the function it calls to return false. I tested this and it works pretty easily:
class Foo < ActiveRecord::Base
validates :field, :presence => true, :if => :stuff
attr_accessor :field
def stuff
return true;
end
end
and then somewhere else:
Foo.class_eval {
def stuff
false
end
}
Why not use #dummy.save_without_validation method to skip validations altogether? I prefer do something like this:
if #dummy.valid?
#dummy.save # no problem saving a valid record
else
if #dummy.errors.size == 1 and #dummy.errors.on(:field)
# skip validations b/c we have exactly one error and it is the validation that we want to skip
#dummy.save_without_validation
end
end
You could put this code in your model or in the controller, depending on your needs.
In Rails 4.1,
I was able to do _validate_callbacks.clear. In my case, I wanted all the validations for a gem removed, so I could create my own. I did this in a module that was patched into the class.
Module #Name
extend ActiveSupport::Concern
included do
_validate_callbacks.clear
#add your own validations now
end
end
Wanted to add that, if you're trying to clear validations on a instance of your Model (not the entire model class), don't do my_dummy._validate_callbacks.clear, as that will clear validations on every instance (and future instance) of your Dummy model class.
For just the instance (and if you wanted to reinstate the validations later), try the following:
Create a copy of the validate callbacks (if you want to reinstate later):
my_dummy_validate_callbacks = my_dummy._validate_callbacks.clone
Set the validate callbacks on your instance to empty:
my_dummy._validate_callbacks = {}
Do what you want on my_dummy validation free!
Reinstate the callbacks: my_dummy._validate_callbacks = my_dummy_validate_callbacks
I'd have to look more into the code and help, but I'm thining it might be possible to inspect the list of validators of the class, and then modify the entry for the validation you want to change to add in an :if => :some_function conditional to it.
You'll need to do it only once for production (so it can be put inside an initializer, but for development you'll need to put it in the model, or somewhere else that will get loaded each time the corresponding model is (perhaps an observer?).
(I'll edit the answer with more information as I come to research it.)
Every Rails validator, pre-defined or custom, is an object, and is expected to respond to #validate(record) method. You can monkey patch or stub this method.
# MyModel.validators_on(:attr1, :attr2, ...) is also useful
validator = MyModel.validators.detect do |v|
validator_i_am_looking_for?(v)
end
def validator.validate(*_)
true
end
# In RSpec you can also consider:
allow(validator).to receive(:validate).and_return(true)
Tested in Rails 5.1.
Don't do this unless you understand what you're doing ;)
This does not directly answer the question but here's an option you should consider in such a situation: instead of disabling validation, you could set the required fields in a before_validation hook.
Since you don't need those required fields, set them with some dummy data that satisfies the validation and forget about them.
No ugly monkey patching.
Assuming the original implementation of Dummy is defined in an engine there is a nasty hack that will do what you want. Define Dummy in your application to keep the original implementation of Dummy from being auto-loaded. Then load the source to Dummy and remove the line that does the validation. Eval the modified source.
Put the following in your app/models/dummy.rb
class Dummy < ActiveRecord::Base
end
# Replace DummyPlugin with name of engine
engine = Rails::Application::Railties.engines.find { |e| e.class == DummyPlugin::Engine }
dummy_source = File.read File.join(engine.config.root, "app", "models", "dummy.rb")
dummy_source = dummy_source.gsub(/validates :field, :presence => true.*/, "")
eval dummy_source
If it is regular gem instead of an engine the same concept would apply, just would need to load the source for Dummy from the gem root instead of the engine root.

Is there a way to rename ActiveRecord model columns?

I am thinking about choosing rails' ActiveRecord to have access to a legacy database. It's names are really confusing, so it wouldn't be a good idea to use its column names in the model.
Setting table name is really easy. But do I have a way to rename column name, only in the model?
Convention over configuration is great, but in this case I can't change legacy database names.
Using alias_attribute from ActiveSupport doesn't solve my problem, since the object still shows the legacy column names when being serialized or printed. I need to return these models in JSON format, for instance, and alias_attribute wouldn't be suitable for this.
use alias_attribute in your model. e.g.
alias_attribute :new_column_name, :column_name_in_db
for more details, refer to : https://stackoverflow.com/a/4017071/445908
What did I do to achieve this?
The code below overwrites default ActiveModel::Serialization's serializable_hash, converting column names in it. Not complete, maybe some refactoring would be nice, but it's working ;)
model example:
class Account < ActiveRecord::Base
include ActiveModel::ColumnNaming
set_table_name 'conta_tbl'
set_primary_key 'cod_conta'
rename_columns ({
id: 'cod_conta',
billing_group_id: 'id_cobranca',
invoice_id: 'cod_pagamento'
})
end
code:
module ActiveModel
module ColumnNaming
extend ActiveSupport::Concern
def serializable_hash(options = nil)
hash = super(options)
self.class.columns_map.each do |legacy, renamed|
hash[renamed] = hash.delete(legacy)
end
hash
end
module ClassMethods
def columns_map
#columns_map
end
def rename_columns(map)
#columns_map = map.invert
columns_map.each { |key, value| alias_attribute value.to_sym, key.to_sym }
end
end
end
end

Resources