Raise validation error from virtual attribute in Rails - ruby-on-rails

I got two models: Source and SourceType. Source of course belongs to SourceType.
I want to create new source and assign proper sourcetype object to it. 'Proper' means that one virtual attribute of the source object match some of sourceType objects test regexpression, which becomes source's Type.
I got an attribute writer in source object
class Source < ActiveRecord::Base
belongs_to :source_type
def url=(value)
SourceType.each do |type|
# here i match type's regexp to input value and if match,
# assign it to the new source object
end
end
end
I don't want to build any custom validator for it'll be need to run through SourceTypes twice. How to raise validate error if no sourcetypes is fit to the input so user could see error reasons in a form?

Validation
If you set the virtual attribute using attr_accessor, you should be able to validate on the model you're sending data to (alternatively using inverse_of if you'd like to validate on the nested model):
http://api.rubyonrails.org/classes/ActiveModel/Validator.html
This can now be used in combination with the validates method (see ActiveModel::Validations::ClassMethods.validates for more on this).
class Person
include ActiveModel::Validations
attr_accessor :title
validates :title, presence: true
end
Code
I'd do this:
class Source < ActiveRecord::Base
belongs_to :source_type, inverse_of: :sources
attr_accessor :url
end
class SourceType < ActiveRecord::Base
has_many :sources, inverse_of: :source_type
validates :source_type_attr, presence: { if: :url_match? }
def url_match?
self.sources.url == [your_regex]
end
end

Related

Virtual attributes Rails - Unknown Attribute Error

I'm making a Rails 4 application. Each incident belongs to a user. Each incident has many events. I want that I can create an Incident with current_user.incidents.new and pass it an attribute message which exists in the Event model. On creation, I wish to create a new event with the said message.
Here's my Incident model.
class Incident < ActiveRecord::Base
# The 'incident' model.
# Incidents are created by Users (belongs_to :user)
# Incidents may be public or private.
# Incidents have many events (identified, fixing, etc.)
belongs_to :user
has_many :events
validates :name, presence: true, length: {maximum: 256}
validates_presence_of :component
validates_presence_of :public
validates_presence_of :user_id
attr_accessor :message
validates_associated :message, presence: true
def message
Event.find_by_incident_id(self.id).message
end
def message=(value)
self.events.new(message: value, status: self.status)
end
private
def incident_params
params.require(:incident).permit(:name, :status, :user_id, :message)
end
end
But, when I run #i = Incident.new(message: 'something'), I get ActiveRecord::UnknownAttributeError: unknown attribute 'message' for Incident.
Please help me figure this out.
The problem is that you're passing the values to the default ActiveRecord::new method for Incident, which won't care about your getters, setters and accessors, it goes straight to the columns.
Some people override or adapt the build method that can implement clever logic like you require, without affecting the base initialize method.
Because your Incident model inherits from ActiveRecord::Base, it inherits the constructor. The ActiveRecord constructor only accepts model attributes (db columns) as params. You are trying to call an arbitrary instance method. You could create a class method that creates an instance then adds the message:
def self.create_with_message(msg)
i = Incident.new
i.message(msg)
return(i)
end

Hide record in Rails model if nested_attributes are empty

I have two models in my Rails app: Listing and ListingPhoto. I want to set some kind of scope on my model that removes Listings that have no nested ListingPhotos. These are being pulled in from an external API, so I have no easy way to control these saving.
Listing looks like this:
class Listing < ActiveRecord::Base
acts_as_taggable_on :listing_types
has_many :listing_photos
accepts_nested_attributes_for :listing_photos, allow_destroy: true
private
end
ListingPhoto looks like this:
class ListingPhoto < ActiveRecord::Base
belongs_to :listing
validates :listing, presence: true
mount_uploader :photo, PhotoUploader
end
What would I need to add to my Listing model to stop listings with empty photo sets showing up?
you can add static method in Listing model:
def self.with_photos
includes(:listing_photos).where.not(:listing_photos => {:listing_id => nil})
end
and then just easily call:
Listing.with_photos

Editing a has_one association in ActiveAdmin - avoid saving when nothing is entered

I've got a model in which a very small percentage of the objects will have a rather large descriptive text. Trying to keep my database somewhat normalized, I wanted to extract this descriptive text to a separate model, but I'm having trouble creating a sensible workflow in ActiveAdmin.
My models look like this:
class Person < ActiveRecord::Base
has_one :long_description
end
class LongDescription < ActiveRecord::Base
attr_accessible :text, :person_id
belongs_to :person
validates :text, presence: true
end
Currently I've created a form for editing the Person model, looking somewhat like this:
form do |f|
...
f.inputs :for => [
:long_description,
f.object.long_description || LongDescription.new
] do |ld_f|
ld_f.input :text
end
f.actions
end
This works for adding/editing the LongDescription object, but I still have an issue: I'd like to avoid validating/creating the LongDescription object if no text is entered.
Anyone with better ActiveAdmin skills than me know how to achieve this?
Are you using accepts_nested_attributes_for :long_description? If so, you can add a :reject_if option:
class Person < ActiveRecord::Base
has_one :long_description
accepts_nested_attributes_for :long_description, reject_if: proc { |attrs| attrs['text'].blank? }
end
Note that this is a Rails thing, not an ActiveAdmin thing, and so it will simply skip assignment and update/create of the nested object if that attribute is missing.
More here: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Possible to specify what fields a belongs_to or has_many relationship object(s) return(s)?

A Contact has a User assigned to them:
class Contact < ActiveRecord::Base
...
belongs_to :user
...
end
The user model has a field I want to exclude any time a user object or objects are returned from db. One of the ways to make it work is to add a default scope:
class User < ActiveRecord::Base
...
has_many :contacts
...
default_scope select((column_names - ['encrypted_password']).map { |column_name| "`#{table_name}`.`#{column_name}`"})
end
So in console if I do:
User.first
The select statement and result set do not include 'encrypted_password'.
However, if I do:
c = Contact.includes(:user).first
c.user
they do. The default scope on the User model does not get applied in this case and the 'encrypted_password' field is shown.
So my question is why? And also, is there a clean way to specify what fields should be returned on related object(s)?
You should just be able to use the :select option on the belongs_to relationship. Something like this:
class Contact < ActiveRecord::Base
...
belongs_to :user, :select => [:id, :first_name, :last_name, :email]
...
end

Rails 3: validates_presence_of validation errors on default value and in associated model

I have a basic invoice setup with models: Invoice, Item, LineItems.
# invoice.rb
class Invoice < ActiveRecord::Base
has_many :line_items, :dependent => :destroy
validates_presence_of :status
before_save :default_values
def default_values
self.status = 'sent' unless self.status
end
end
# item.rb
class Item < ActiveRecord::Base
has_many :line_items
validates_presence_of :name, :price
end
# line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :item
belongs_to :invoice
before_save :default_values
validates_presence_of :invoice_id
validates :item_id, :presence => true
end
There is more in the model but I only presented the above for simplicity.
I get the following errors:
2 errors prohibited this invoice from being saved:
Line items invoice can't be blank
Status can't be blank
So two problems:
If I remove validates :invoice_id, :presence => true I don't get the Line items invoice can't be blank error message anymore, but why? I do want to validate the invoice_id on line_items, ALL line_items are supposed to have an invoice_id. How can I validate the invoice_id on line_items without getting an error?
Why am I getting the Status can't be blank error if I set it as a default value? I can probably set it up on the invoices_controller but I think the default value should be set in the model, right? How can I validate the presence of status and still have a default value in the model for it?
Both of these validation errors are occurring because validations get called before save (and before the before_save callback).
I'm assuming that you're using a nested_form to create the invoice and it's line items at the same time. If this is the case, you don't want to validates :invoice_id, :presence => true on the line items - the invoice and the line items are coming in at the same time, and the invoice hasn't been saved yet, so it doesn't have an id. If you leave the validation in, you'll need to create and save an empty invoice first, and then create the line items later so the invoice_id is available. If you only want to make sure invoice_id is still set after any edits, you can enforce this via validates :invoice_id, :presence => true, :on => :update this will skip the validation when the line item is being created (and the invoice_id isn't available yet).
You're running into problems with validates :status, :presence => true for similar reasons - the values coming in via the request are being validated against, and the "status" value isn't there. The before_save callback runs after validation. You can set the default value in the before_validation or after_initialization callback and the values will be there when validations are run.
Check out the Callbacks documentation for Rails for more info.
I'll start with 2:
before save is being executed only before save, meaning, after the object passed validation and is about to be saved. If the validation fails - it won't be executed.
as for 1:
Can you give an example of how you're trying to create an invoice?
Problem 1
Try validates_associated which checks that the associated models are all valid
Problem 2
Like most of the answers say before_save gets called after validations. The magic you're looking for is after_initialize which gets run after an object's initialize method is called.
class Invoice < ActiveRecord::Base
after_initialize :default_values
validates :status, presence: true
private
def default_values
self.status ||= 'sent'
end
end

Resources