Custom constraints - postgres - ruby-on-rails

This is more of a conceptual doubt. Is there any way in which we can apply a constraint that value of a field can't be blank? I know NOT NULL can be used. But I want to check if the field has only spaces, it rejects that value also. For example " " should be rejected.

In your Model's class, you can add a validation like this:
validates :field_name, presence: true
From the Rails Documentation:
This helper validates that the specified attributes are not empty. It
uses the blank? method to check if the value is either nil or a blank
string, that is, a string that is either empty or consists of
whitespace.
That will ensure that the value of that field can't be blank.
When validations are run?
Creating and saving a new record will send an SQL INSERT operation to
the database. Updating an existing record will send an SQL UPDATE
operation instead. Validations are typically run before these commands
are sent to the database. If any validations fail, the object will be
marked as invalid and Active Record will not perform the INSERT or
UPDATE operation. This avoids storing an invalid object in the
database.
See this for more details.

You could add the next psql constraints to prevent inserting null or " " strings in your DB without changing an application logics.
CREATE TABLE test (
string character varying(16) NOT NULL,
CONSTRAINT good_string CHECK (rtrim(string, ' ') != '')
);

Related

Find_or_initialize_by(nil) returns last record

So I'm trying to create a complicated(imo) CSV/Excel import process, which has to create records for 3 separate models + associations, and while debugging the validation for this process I have stumbled upon a confusing concept regarding find_or_initialize_by() when the passed attributes are nil.
According to Rails API Docs, find_or_initialize_by() should do the following:
# File activerecord/lib/active_record/relation.rb, line 222
def find_or_initialize_by(attributes, &block)
find_by(attributes) || new(attributes, &block)
end
Which leads me to believe that if I pass attributes to find_by which are nil or even blank it should then move it to Model.new(nil), however, for me it keeps returning the last record for that model.
Methods such as Model.find_by() and Model.where().find_or_initialize when passed nil, {}, false, "" still return the last record.
The reason I want it to initialize a new record with nil attributes is so that it fails validation and throws and error back to the user that the data entered in that row is invalid. (Since this is not a standard columns = model attributes type of import, I have to parse the passed columns through another method that should return nil if a piece of the entry is bad... at least thats the best way I can think of.)
So would anyone be able to help me understand why this doesn't work as I've explained and what your suggestion might be in this situation?
Thanks!
Which leads me to believe that if I pass attributes to find_by which
are nil or even blank it should then move it to Model.new(nil),
however, for me it keeps returning the last record for that model.
No, that's not right. find_by() always return first record matching the specified conditions, in your case there is no any conditions, so first record is returned.
It is returns nil only if conditions isn't matching:
=> Model.find_by(nil) # empty conditions without column specifying
=> #<Model:0x00561654201c30
=> Model.find_by(foo: nil) # empty conditions with column specifying
=> nil
In order to initialize model with empty atributes, use empty conditions:
=> Model.where(foo: nil, bar: nil).find_or_initialize

Mongoid - get a field from document

I have the following model
class MyModel
field :title, type: String
field :url, type: String
end
I would like to make a query that will return only the title field for each document.
How can I do it?
Use only or without:
Queryable#only
Limit the fields that are returned to the provided fields.
Queryable#without
Limit the fields that are returned everything except the provided fields.
So you'd say:
MyModel.query_method_calls.only(:title)
# or
MyModel.query_method_calls.without(:url)
Note that only will make a mess of things if you're using Mongoid3 with the Identity Map enabled, you'll get half-formed objects stuck in the identity map cache and end up wondering where all the strange nil attributes are coming from. If this applies to you then bypass Mongoid and use the underlying Moped interface:
Query#select
Select the fields you want returned.
This approach would look like:
MyModel.collection.find(...).select(:title => 1)
You'd be working with hashes instead of Mongoid objects in this case.

rails if statement in controller - checking nil

I have an if statement in my update action in one of my controllers. It looks like this:
if !#bid.attributes.values.include?(nil)
build(#bid.id)
end
I am checking to see if there are any nil valued attributes in my Bid object before building a bid report. I know the build method works fine because it builds a report when not wrapped in the if statement. When it is wrapped in this if statement, it doesn't run. I have checked to make sure that there are no nil values in the object. I went into the rails console and all attributes have non-nil values. In addition, I am able to check this in the views to confirm that there are no nil values.
I have also tried writing as:
build(#bid.id) unless #bid.attributes.values.include?(nil)
and a couple other variations. None are allowing the build to run.
Your code seems fine, I'm betting it's your data that's the problem instead. Mostly likely, assuming this an active record instance, the attribute is id which will be nil until the new record gets saved.
What do you get in the terminal when you add this line right before your if?
puts #bid.attributes.to_yaml
You should be able to see what has values and what does not. And I'm pretty sure at least one of those values is nil.
I would recommend being more explicit about exactly which fields are required. And this is exactly what validations are for.
class Person < ActiveRecord::Base
validates :name, presence: true
end
You explicitly validate each field so that when it's absent you get a very specific error message about why: "Person name can't be blank." So instead of wondering why it wont save, you get told why at the point it fails to save.

Rails 3: update_attributes sets column to 'nil'

The problem I'm having is with the method update_attributes. The code:
n is set to an Active Record object.
n = Notification.find(notification_id)
Then, n is updated with the hash notification_options.
n.update_attributes(notification_options)
The issue I'm having is when I
raise n.inspect
It shows the two fields are set to nil. Also, in the database the two fields are empty.
Why won't it update the attributes?
Let me know if I need to be more specific.
This is because you're using attr_accessor, and not attr_accessible, I would guess. Please show us your Notification model.
So it ends up that the issue was with a line in the model for a gem that being used. It needed a specific format which was causing it to set to nil if it didn't match.

Scope and Field as Parameter in Rails

I've added a scope to a Rails model that allows for searching based on a specified parameter field using a range. Here is what it looks like:
scope :upcoming, lambda { |field|
where("to_char(#{field}, 'DDD') BETWEEN :alpha AND :omega",
alpha: Time.now.advance(days: 4).strftime('%j'),
omega: Time.now.advance(days: 8).strftime('%j'),
)
}
Event.upcoming(:registration) # Query all events with registration shortly.
Event.upcoming(:completion) # Query all events with completion shortly.
The above works fine, however in creating I read the Ruby on Rails Guides and found the following:
Putting the variable directly into the conditions string will pass the variable to the database as-is. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string.
Although the scope is currently never called with a user parameter, I am curious if a way exists of setting the field without using the interpolation in order to better conform with the above recommendation. I've tried using another named parameter, however this will escape the field with quotes (and thus cause it to fail). Any ideas?
I would recommend validating the field parameter against the model's attributes, essentially using the model as a whitelist for values that are allowed to be passed. Something like this:
scope :upcoming, lambda { |field|
if column_names.include? field.to_s
where("to_char(#{field}, 'DDD') BETWEEN :alpha AND :omega",
alpha: Time.now.advance(days: 4).strftime('%j'),
omega: Time.now.advance(days: 8).strftime('%j'),
)
else
# throw some error or return nil
end
}
Okay, reading all the way to the end might help(thanks rubyprince). It looks like you are doing a between query on a field that is storing a date in Oracle. The problem is that to_char is looking for a variable, not a string. And the act of escaping a variable in rails turns it into a string. So, in this particular case you might convert :alpha and :omega into the format of the value stored in field. That way you can escape field in a straightforward manner. Of course there is the issue with Oracle treating dates as Time. I'm guessing that is why you converted to day-of-year for the compare. If you are using the Oracle Enhanced Adaptor you can set
self.emulate_dates_by_column_name = true
to make sure that the field is treated like a date. Then use the to_date function(which takes a string) with :alpha and :omega
scope :upcoming, lambda { |field|
where(":field BETWEEN to_date(:alpha,'yyyy/mm/dd') AND to_date(:omega,'yyyy/mm/dd')",
field: field,
alpha: Time.now.advance(days: 4).strftime('%Y/%m/%d'),
omega: Time.now.advance(days: 8).strftime('%Y/%m/%d'),
)
}
I have no way of testing this so I might be off in the weeds here.
Validating user input as per Jordan is always a good idea.

Resources