Rails add a second field name to validation error message - ruby-on-rails-5

rails 5.2, ruby 2.6.4
A Student record has these fields, in part:
fname,
lname,
emplid,
class_code,
...
The class_code is a digit 1,2,3,4.
1 = freshman
2 = sophomore
3 = junior
4 = senior
I have a validation:
validates_inclusion_of :class_code, :in => Constants::CLASS_CODES,
message: "The class code, %{value}, is not considered a valid class code."
This works, but it would be very helpful if I could tell which student record this error happens on. I would like to add the field emplid to the error message. Is this possible?
Something like:
message: "For student {emplid}, %{value} is not considered a valid class code."
Thanks for any tips.

You can pass a lambda to message.
validates_inclusion_of :class_code, :in => Constants::CLASS_CODES,
message: ->(student, options) { "For student #{student.emplid}, #{options[:value]} is not considered a valid class code." }

Related

SQL injections in Rails 4 issue

I'm trying to learn about SQL injections and have tried to implement these, but when I put this code in my controller:
params[:username] = "johndoe') OR admin = 't' --"
#user_query = User.find(:first, :conditions => "username = '#{params[:username]}'")
I get the following error:
Couldn't find all Users with 'id': (first, {:conditions=>"username = 'johndoe') OR admin = 't' --'"}) (found 0 results, but was looking for 2)
I have created a User Model with the username "johndoe", but I am still getting no proper response. BTW I am using Rails 4.
You're using an ancient Rails syntax. Don't use
find(:first, :condition => <condition>) ...
Instead use
User.where(<condtion>).first
find accepts a list of IDs to lookup records for. You're giving it an ID of :first and an ID of condition: ..., which aren't going to match any records.
User.where(attr1: value, attr2: value2)
or for single items
User.find_by(attr1: value, attr2: value)
Bear in mind that while doing all this, it would be valuable to check what the actual sql statement is by adding "to_sql" to the end of the query method (From what I remember, find_by just does a LIMIT by 1)

Grocery CRUD: How to debug Add / Edit errors

I have a N:M relation beetween 'Museum' and 'Category'. Three tables:
Museum: id, name, ...
Category: id, name, ...
Museum_x_Category: museum_id, category_id
And have set a N:M relation with a sentence like:
$crud->set_relation_n_n('Museum Categories', 'Museum_x_Category', 'Category', 'museum_id', 'category_id', 'name', 'category_id' );
I'm getting "An error ocurred on insert" errors when adding, and "An error has occurred on saving." when editing/uploading.
I guess it is due to an SQL error, and i'd like to see the SQL sentences running behind.
Does anyone know how to see it?
PHP: 5.3.5
MySQL: 5.1.14
Solved it. There were two problems:
1.- there was a non-utf8 character in the relation name:
$crud->set_relation_n_n('Categorías', 'Museum_x_Category', 'Category', 'museum_id', 'category_id', 'name', 'category_id' );
now replaced by:
$crud->set_relation_n_n('Categorias', 'Museum_x_Category', 'Category', 'museum_id', 'category_id', 'name' );
(note the í in Categorías, which means Categories in Spanish).
2.- there was a problem with the last parameter ('category_id'). Note that i've removed it. With the parameter included, it was assigning all the museums to the first category, always, whatever category i select.
It works as desired now :)

How to force rails to store empty strings?

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.

Ruby datamapper - search in flag value

I have a user table (datamapper model) that has a column called permission which contains the bitmask value.
property :permission, Flag[:perm1, :perm2, :perm3]
I want to find all the users who have certain permissions, such as perm1 and perm2
so I call,
User.all(:permission => [:perm1, :perm2])
This makes query
select * from user where permission = 3 that is incorrect.
while correct query should have been (because it is type - flag)
select * from user where permission &1 != 0 and permission &2 != 0
Does anyone in ruby datamapper, how to make the call to search in flag values.
I could not find any direct way to do it. So did use this hack.
User.all(:conditions => ['permission & ? != 0 and permission & ? != 0', 1,2])
Which version are you running? Under 1.2, I get SELECT ... FROM "users" WHERE "permission" IN (1, 2) ... with User.all(:permission => [:perm1, :perm2]).
One option is to make a union: User.all(:permission => :perm1) | User.all(:permission => :perm2).
Or maybe shortened to User.perm1s | User.perm2s by class methods:
class User
# ...
def self.perm1s; all :permission => :perm1 end
def self.perm2s; all :permission => :perm2 end
end
Not exactly the same query with either one as you've shown, but the result should be the same.

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