How to force rails to store empty strings? - ruby-on-rails-3

I'm asking the opposite of the question to force empty strings to be NULL; instead, I want fields that are empty strings to be stored as empty strings. The reason I want to do so (even in contradiction to what some people say) is that I want to have a partial uniqueness constraint on the table that works for multiple database types (postgres, mysql, etc), as described in this question here.
The psuedocode for the schema is basically:
Person {
first_name : String, presence: true
middle_name : String, presence: true
last_name : String, presence: true
birth_date : String, presence: true
city_of_birth: String, presence: true
active: tinyint
}
The constraint is that a person must be unique if they are active; inactive people can be not unique (ie, I can have multiple John Smiths that are not active, but only one active John Smith).
Further complication: According to the project specification, only first_name and last_name are required to be given by the user, all other fields can be blank.
Our current solution for applying the partial uniqueness constraint is to use the fact that NULL != NULL, and set the active tinyint to NULL if someone is not active and set it to 1 if they are active. Thus, we can use this rails code in the migration:
add_index :Persons, [first_name, middle_name, last_name, birth_date,
city_of_birth, active], unique:true, name: "unique_person_constraint"
In order for this constraint to work, however, none of the other fields can be NULL. If they are, then two John Smiths with no other filled in fields and active = 1 will still be 'unique' because the middle_name fields with value NULL will be different from each other (because NULL != NULL, regardless of column type).
However, when I do
options = { first_name: "John",
middle_name: "",
last_name: "Smith",
birth_date: "",
city_of_birth: "",
}
person = Person.new(options)
success = person.valid?
success is always false because
Middle name can't be blank
City of birth can't be blank
Birth date can't be blank
So I need a way to ensure that I always have at least empty strings for those other fields to enforce that partial uniqueness constraint. How can I do this? If I get rid of the presence:true in the model definition, then it appears that NULL fields are now allowed, which is bad.
This is Rails 3.2.13, and I can give other gems and gem versions if necessary.

Along with presence true, you can also add allow_blank as true which would allow you save empty strings.

Related

Django ImageField null=True doesn't make blank field equal to NULL

I have a model with this field:
carousel_image = models.ImageField(upload_to='news/%Y/%m/%d, null=True, blank=True)
I was wondering why exclude(carousel_image__isnull=True) and other queries that check if field is or isn't null aren't working.
I checked my sqlite3 db and received this:
sqlite> select carousel_image from asgeos_site_news;
news/2020/12/23/index.jpeg
news/2020/12/23/gradient-1.jpeg
( 3 blank lines )
I also tried adding WHERE carousel_image = NULL and it returned nothing.
Why my images are not null? They're just a blank lines. I have to use carousel_image__exact='' to exclude them right now.
File fields can't be set to null in the db, here's the reference from django documentation
blank = True
is enough.
blank is stored as ''

How to INSERT a reference to UUID from another table in PostgreSQL?

I'm learning to use Sequelize to use with a PostgreSQL database. All of the following is happening on a dev. environment. This happened while manually trying to insert data into my tables to check if things are setup correctly through Sequelize, check on failing unit tests, etc.
I've made two tables with Sequelize models: User and Publication. Both these tables are generating UUIDv4. I've associated the User hasMany Publications, and Publication belongsTo User (you may reference the extra info).
On my psql shell, I've inserted the following record to my User table (rest of the data cut out for brevity):
| id | firstName | lastName | ..|
|----------------------------------------|------------|-----------|---|
| 8c878e6f-ee13-4a37-a208-7510c2638944 | Aiz | .... |...|
Now I'm trying to insert a record into my Publication table while referencing my newly created user above. Here's what I entered into the shell:
INSERT INTO "Publications"("title", "fileLocation", ..., "userId")VALUES('How to Pasta', 'www.pasta.com', ..., 8c878e6f-ee13-4a37-a208-7510c2638944);
It fails and I receive the following error:
ERROR: syntax error at or near "c878e6f"
LINE 1: ...8c878e6f-ee...
(it points to the second character on the terminal in LINE 1 reference - the 'c').
What's wrong here? Are we supposed to enter UUIDs another way if we want to do it manually in psql? Do we paste the referenced UUID as a string? Is there a correct way I'm missing from my own research?
Some extra info if it helps:
From my models:
Publication.associate = function(models) {
// associations can be defined here
Publication.belongsTo(models.User, {
foreignKey: "userId"
});
};
and
User.associate = function(models) {
// associations can be defined here
User.hasMany(models.Publication, {
foreignKey: "userId",
as: "publications"
});
};
Here's how I've defined userId in Publication:
userId: {
type: DataTypes.UUID,
references: {
model: "User",
key: "id",
as: "userId"
}
}
If it's worth anything, my (primaryKey) id on both models are type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 (I don't know if this is an issue).
surround your uuid in apostrophes (write it as a string) and pg will convert it to a uuid
Starting and ending your string with {} is optional
Eg
INSERT INTO "Publications"("title", "fileLocation", ..., "userId")VALUES('How to Pasta', 'www.pasta.com', ..., '8c878e6f-ee13-4a37-a208-7510c2638944');
Or
INSERT INTO "Publications"("title", "fileLocation", ..., "userId")VALUES('How to Pasta', 'www.pasta.com', ..., '{8c878e6f-ee13-4a37-a208-7510c2638944}');
Source (I don't do pgsql much so I casted around for another person who wrote some working pgsql. If this doesn't work out for you let me know and I'll remove the answer): PostgreSQL 9.3: How to insert upper case UUID into table

Writing SQL in Rails 4

I have a payment_request model and a payment_detail model. In the payment_request index I need to be able to search by first and last name which are stored in the payment_details table. I am newish to writing SQL and could use some help. I have what I believe to be the correct query below, but am not sure how to write that in my Rails controller so I can search by name.
SELECT first_name, last_name
FROM payment_details
LEFT OUTER JOIN payment_requests
ON payment_requests.id = payment_details.payment_request_id;
If you're using ActiveRecord models, you can skip all that and build that query with the ActiveRecord Querying Interface.
#payment_requests = PaymentRequest.joins(:payment_detail).where(payment_detail: {first_name: params[:first_name], last_name: params[:last_name]})
If you intent to show payment_details data on that index page, you should consider including that information in that query, so you avoid n+1 queries.
#payment_requests = PaymentRequest.includes(:payment_detail).where(payment_detail: { first_name: params[:first_name], last_name: params[:last_name]})
Note: You've got to have a complete match to use the above, so it may not be what you want.
I'd also recommend you use the Ransack gem to build complex queries. It would go something like this:
PaymentRequest.ransack(params[:q])
and in your views:
<%= f.search_field :payment_detail_first_name_or_payment_detail_last_name_cont %>
That would allow you to use just one field to query both columns.
You can do the following:
term_to_find = params[:search]
columns_to_search = %w( payment_details.first_name payment_details.last_name )
sql_conditions = []
columns_to_search.map |column_name|
sql_conditions.push("#{column_name} ILIKE :term_to_find")
end
PaymentRequest.includes(:payment_details)
.where(sql_conditions.join(' OR '), term_to_find: "%#{term_to_find}%")
This will find results containing the string you searched. Example: you typed "bob" in the search, it could find "bobby" or even "Mr. Bob" (the ILIKE makes the search case-insensitive)

Rails find method :select alias as id?

I currently have a .find method in one of my rails controller actions - the relevant part is:
.find(:all, :select => 'last_name as id, last_name as name')
I am getting some odd behaviour trying to alias the last_name column as id - if I alias it as anything else, it works fine (i can do last_name as xyz and it outputs the last name in a column called xyz, but as I am using this to populate a drop-down where I need to have the name in the id column, i need it to be called 'id').
I should point out that it does output an id column, but it is always "id":0.
Could anyone shed any light on what I need to do to get this column aliased as 'id'?
Thanks!
I'm not sure of how you can do this in a Rails query statement. Rails is going to try and take over the id column, casting the value returned by the database as id with the type of column that id is (presumably integer). That's why your id column keeps getting set to 0, because "string".to_i #=> 0
However, there is a way to do it, once you have the results back.
Since you have the question tagged as Rails 3, it is preferable to use the new ActiveRelation syntax. You can do the following:
# First, get the results from the query, then loop through all of them.
Customer.select("last_name as 'ln', last_name as 'name'").all.collect do |c|
# The first step of the loop is to get the attributes into a hash form
h = c.attributes
# The next step is to create an "id" key in the hash.
# The Hash#delete method deletes the key/value pair at the key specified and returns the value.
# We'll take that returned value and assign it to the just created "id" key.
h["id"] = h.delete("ln")
# And we have to call out the hash to ensure that it's the returned value from the collect.
h
end
That will get you a hash with the id value as the text string value last_name and a name value as the same.
Hope that helps!
You shouldn't need to setup aliases in the finder SQL just to populate a drop-down. Instead simply use the last_name value for the value attribute (as well as the display text).
Eg if you're using the collection_select helper:
<%= f.collection_select :attribute_id, #collection, :last_name, :last_name %>

ActiveRecord: List columns in table from console

I know that you can ask ActiveRecord to list tables in console using:
ActiveRecord::Base.connection.tables
Is there a command that would list the columns in a given table?
This will list the column_names from a table
Model.column_names
e.g. User.column_names
This gets the columns, not just the column names and uses ActiveRecord::Base::Connection, so no models are necessary. Handy for quickly outputting the structure of a db.
ActiveRecord::Base.connection.tables.each do |table_name|
puts table_name
ActiveRecord::Base.connection.columns(table_name).each do |c|
puts "- #{c.name}: #{c.type} #{c.limit}"
end
end
Sample output: http://screencast.com/t/EsNlvJEqM
Using rails three you can just type the model name:
> User
gives:
User(id: integer, name: string, email: string, etc...)
In rails four, you need to establish a connection first:
irb(main):001:0> User
=> User (call 'User.connection' to establish a connection)
irb(main):002:0> User.connection; nil #call nil to stop repl spitting out the connection object (long)
=> nil
irb(main):003:0> User
User(id: integer, name: string, email: string, etc...)
If you are comfortable with SQL commands, you can enter your app's folder and run rails db, which is a brief form of rails dbconsole. It will enter the shell of your database, whether it is sqlite or mysql.
Then, you can query the table columns using sql command like:
pragma table_info(your_table);
complementing this useful information, for example using rails console o rails dbconsole:
Student is my Model, using rails console:
$ rails console
> Student.column_names
=> ["id", "name", "surname", "created_at", "updated_at"]
> Student
=> Student(id: integer, name: string, surname: string, created_at: datetime, updated_at: datetime)
Other option using SQLite through Rails:
$ rails dbconsole
sqlite> .help
sqlite> .table
ar_internal_metadata relatives schools
relationships schema_migrations students
sqlite> .schema students
CREATE TABLE "students" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "surname" varchar, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
Finally for more information.
sqlite> .help
Hope this helps!
You can run rails dbconsole in you command line tool to open sqlite console. Then type in .tables to list all the tables and .fullschema to get a list of all tables with column names and types.
To list the columns in a table I usually go with this:
Model.column_names.sort.
i.e. Orders.column_names.sort
Sorting the column names makes it easy to find what you are looking for.
For more information on each of the columns use this:
Model.columns.map{|column| [column.name, column.sql_type]}.to_h.
This will provide a nice hash.
for example:
{
id => int(4),
created_at => datetime
}
For a more compact format, and less typing just:
Portfolio.column_types
I am using rails 6.1 and have built a simple rake task for this.
You can invoke this from the cli using rails db:list[users] if you want a simple output with field names. If you want all the details then do rails db:list[users,1].
I constructed this from this question How to pass command line arguments to a rake task about passing command line arguments to rake tasks. I also built on #aaron-henderson's answer above.
# run like `rails db:list[users]`, `rails db:list[users,1]`, `RAILS_ENV=development rails db:list[users]` etc
namespace :db do
desc "list fields/details on a model"
task :list, [:model, :details] => [:environment] do |task, args|
model = args[:model]
if !args[:details].present?
model.camelize.constantize.column_names.each do |column_name|
puts column_name
end
else
ActiveRecord::Base.connection.tables.each do |table_name|
next if table_name != model.underscore.pluralize
ActiveRecord::Base.connection.columns(table_name).each do |c|
puts "Name: #{c.name} | Type: #{c.type} | Default: #{c.default} | Limit: #{c.limit} | Precision: #{c.precision} | Scale: #{c.scale} | Nullable: #{c.null} "
end
end
end
end
end