I want to implement a character limit to a data value on my PostgreSQL database for my Ruby on Rails application. Currently my database table looks like this:
create_table "applications", force: :cascade do |t|
t.string "name"
t.string "gender"
t.date "date_of_birth"
t.string "gpa"
t.text "essay"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
I want to change it so the "essay" only allows 10000 characters. From my limited understanding of PostgreSQL, text is unlimited by default whereas string is for 255 characters or less. I thought about implementing a Javascript conditional to not let the user hit the submission button in the client if the text is over 1,000 characters, but of course a tech savvy user could change that. What would be the most optimal way to do this?
Use rails model validations - validates_length_of.
On the rails end you can add this in application.rb file
validates_length_of :essay, :maximum => 1000
This will ensure that max size is not exceded. Also keep the javascript validation, since its helpful for all users except the malicious once.
Also I recommend you to change the table name from applications. You can get easily confused
Related
Given a model with multiple has_many associations like this:
class Route < ActiveRecord::Base
has_many :flights
has_many :deals
has_many :ratings
...
end
Given a scenario where not all routes actually have all 3, is there an easy way to find the number of routes that do have all 3?
Based on other SO questions I have tried this:
scope :active, -> { joins(:ratings).joins(:deals).joins(:flights). Then I call Route.active.count in my console but the process just hangs. These tables are pretty large so I'm assuming it has something to do with it. Flights has 2,037,031 rows; Deals has 659,804 rows; and Ratings has 141,879.
Is there a faster/better way to get to the number I need (number of routes that have flights, deals, and ratings)?
EDIT
Here is the relevant schema info:
create_table "routes", force: true do |t|
t.integer "from_id"
t.integer "to_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "routes", ["from_id"], name: "index_routes_on_from_id", using: :btree
add_index "routes", ["to_id"], name: "index_routes_on_to_id", using: :btree
create_table "ratings", force: true do |t|
# various columns...
t.datetime "created_at"
t.datetime "updated_at"
t.integer "route_id"
end
add_index "ratings", ["route_id"], name: "index_ratings_on_route_id", using: :btree
create_table "flights", force: true do |t|
t.integer "airline_id"
t.integer "route_id"
# various columns...
end
add_index "flights", ["airline_id"], name: "index_flights_on_airline_id", using: :btree
add_index "flights", ["route_id"], name: "index_flights_on_route_id", using: :btree
create_table "deals", force: true do |t|
t.integer "route_id"
# various columns...
end
add_index "deals", ["route_id"], name: "index_deals_on_route_id", using: :btree
EDIT
I added boolean attributes to the Routes table of flightless and ratingless to help keep easier track after an import of whether the Route has any flights or ratings. After doing that, I've tried a few different queries and get different counts:
[7] pry(main)> Route.where(flightless: false, ratingless: false).includes(:deals).count
=> 19415
[8] pry(main)> Route.where(flightless: false, ratingless: false).joins(:deals).distinct.count
=> 10243
[9] pry(main)> Route.where(flightless: false, ratingless: false).joins(:deals).count
=> 378737
The query on line 8 produces the same result as Nic's pure SQL suggestion in the first answer below. I think I understand why the query on line 9 returns way more than could possible be correct (each route is duplicated for every resource it gets joined to) but I don't understand the difference between joins and includes well enough to tell why joins...distinct produces a different answer than includes here.
Using joins as well as includes will generate a cartesian product which will require some memory and depending on available resources can make the server unresponsive. Since you're after count and not the actual records, it should be enough to check for their existence, albeit probably at the expense of more calculations:
Route.where('EXISTS (SELECT 1 FROM ratings WHERE routes.id = ratings.route_id)
AND EXISTS (SELECT 1 FROM deals WHERE routes.id = deals.route_id)
AND EXISTS (SELECT 1 FROM flights WHERE routes.id = flights.route_id)').count
Although this mostly is raw SQL, it will return a proper ActiveRecord::Relation when count is skipped. This allows using it for on-demand loading of models like in find_in_batches.
This most probably can be rewritten using Arel to be more Rails way, but at this point I'm curious whether it actually gets you the proper result.
EDIT:
Or you could use
Route.joins(:ratings).joins(:deals).joins(:flights).distinct.count
It will return the correct count but will produce an intermediary cartesian product which it will then reduce with distinct. I would be curious to know what is the performance difference between these two approaches.
I'm building a Ruby on Rails application where I have a Locomotive and a Trip model.
Each locomotive can have many trips, and each trip belongs to one locomotive. The Trip model has a "begin" and "end" attribute, which are timestamps and can be null. Trip "end" is set once a locomotive on arrival of its destination; until then its' value is null.
Troubles start whenever I want to set the "end" attribute on the last trip for a particular locomotive ( id=45, for example). To this I must search within the set of all trips for a match on "locomotive_id=45" and search for those with a null "end" attribute. Only then I may be able to set the ending time.
In order to improve performance, I'm thinking of adding circular association with both models. Add a column to my Locomotive table named "last_trip_id", which can be null, that points to the Trip table and lets me know which row on that table is the last trip this locomotive has done.
I think this idea is awesome! :D However, I haven't found any documentation or tips regarding circular association on the ruby association guides or in it's API... So I don't know if this sort of implementation is encouraged on the RoR framework or not.
Would somebody give me some tips about this issue? Is it encouraged to add this circular association in order to have a better performance within this context?
Right now, my Locomotive and Trip models looks like this:
app/models/locomotive.rb
has_many :trips, dependent: :restrict_with_error
app/models/trip.rb
validates :locomotive_id, presence: true
belongs_to :locomotive
I think my new models should look like this:
app/models/locomotive.rb
has_many :trips, dependent: :restrict_with_error
belongs_to trip
app/models/trip.rb
validates :locomotive_id, presence: true
belongs_to :locomotive
has_one :locomotive
Which methods will add rails to my new models? Would I have some troubles when I write, for example:
> L=Locomotive.first
> L.trips.locomotives.trips.....
Best regards,
Karl
EDIT 1
Here you have my locomotives and trips structure:
db/schema.rb
...
create_table "locomotives", force: true do |t|
t.string "code"
t.datetime "created_at"
t.datetime "updated_at"
end
...
create_table "trips", force: true do |t|
t.integer "locomotive_id"
t.datetime "begin"
t.datetime "end"
t.datetime "created_at"
t.datetime "updated_at"
t.string "city_origin"
t.string "city_destination"
end
I would structure the schema like so: remember that with your schema you are trying to model real world concepts as clearly as possible, so always start with real world intuitive concepts.
Station #list of stations
Locomotive #list of trains
#these are the **scheduled** journeys
Route
start_station_id
end_station_id
departure_time
arrival_time
#these are the **actual** journeys
Journey
route_id
locomotive_id
actual_departure_time
actual_arrival_time
So, Stations, Locomotives and Routes all exist in advance, and then a Journey record is created for every actual real life journey by a train. The journey route_id and locomotive_id are filled when the journey is scheduled, and actual_departure_time and actual_arrival_time are filled in when the train actually leaves and arrives.
I think the names of the classes could be a bit better, and Journey could be decomposed further so that you have a table of start & end stations, and another with instances of these at different times, but you get the idea hopefully.
Your idea is to have an extra field from locomotive to the latest trip, so performance is better. If you set the right index on the trips table, maybe you don't need it.
Nevertheless, if you want to do it, here is how. Rails has the standard to set the name of the association to be identical to the classname. But this is no law. If your model get a little more complex, this is often no longer the right thing. So you can override it.
You call the association "latest trip" (or current_trip ?) and tell rails that it is actually a Trip-object.
locomotive.rb
belongs_to :latest_trip,
class_name: "Trip",
foreign_key: "latest_trip_id"
http://guides.rubyonrails.org/association_basics.html#belongs-to-association-reference
And you need to add a field "latest_trip_id" to the locomotives database table. You only need to be careful when you create and the objects, that all fields are correctly set.
You can have even several associations to Trip, like "funniest_trip", "accident_trips",....
You could introduce a Schedule model that would hold these trips. This would allow you to sort by date range for locomotives arriving/departing.
Just take care if your modeling trips that may run 'late' or 'off' schedule.
class CreateTrainManagementSystem < ActiveRecord::Migration
def change
create_table :locomotives do |t|
t.float :speed
end
create_table :schedules do |t|
t.integer :locomotive_id
t.integer :route_id
t.datetime :departure
t.datetime :arrival
end
create_table :routes do |t|
t.integer :origin
t.integer :destination
t.float :distance
end
create_table :trips do |t|
t.integer :schedule_id
t.datetime :departure
t.datetime :arrival
end
create_table :stations do |t|
t.string :title
t.string :address
t.string :city
t.string :state
t.integer :zip
t.decimal :coord_x
t.decimal :coord_y
end
end
end
To own this solution you can't just copy and paste it. This still needs models, validations and the like. It also solves more than just the performance issue. Think about calculating ETAs for delayed trains using speed, trip log, and route distances.
#Karl Hi, just saw in your comments that imply you're either a student or recent graduate. This is great news, you have a great resource here to ask questions. If you decide this is your craft and want to create solutions like mine, read Joe Celko's book Thinking In Sets.
The problem that i have is when i run my migrations the updates is applied to the database, but rails does not do the same.
To be more accurate. I have a address model with a house number. Recetly i was told that the house number should be able to contain letters, like (35B). Therefore i would like to convert the integer colum to a column of strings. This is no problem in any case with my data, red. only integers.
The migration that i applied works as expected. It changes the type of the column in the postgres database and preserves the data content. I was using this migration.
class ConvertIntToStringOnNumber < ActiveRecord::Migration
def change
change_table :addresses do |t|
t.change :number, :string
end
end
end
Migration result with this schema.rb
create_table "addresses", :force => true do |t|
t.string "street"
t.string "number"
t.integer "zip"
t.string "floor"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
After running the migration on my heroku server i am not able to query the database using data from the form, this was no problem before. Rails is trying to look for an integer, but the database is containing strings.
Rails is trying to run this query, even though the schema.rb says something different. This is where the party stops.
SELECT "addresses".* FROM "addresses" WHERE "addresses"."street" = 'xxxx' AND "addresses"."number" = 63 AND "addresses"."floor" = '' AND "addresses"."zip" = 9000 LIMIT 1):
I have seen a lot of problems with forigen keys, but this is NOT one of those problems.
Did you restart your application after running the migration?
ActiveRecord loads information about your tables into each class when they are instantiated. See #columns for more info. Since Heroku runs your app in production mode, your classes won't be automatically reloaded on each request.
Try running heroku restart on your application - Rails should pick up the changes then.
I'm building an employments table which contains start_date and end_date
create_table :employments do |t|
t.integer :user_id
t.integer :company_id
t.string :title
t.string :department
t.string :summary
t.date :start_date
t.date :end_date
t.timestamps
end
It's good for creating past employments, but not for current employment since the user is still employed.
How to handle when user tries to indicate he/she is currently employed, so that the user doesn't have to select the :end_date. (I know LinkedIn offers a option that allows users to click on "I'm currently working here" to avoid choosing :end_date, but in database, how is this achieved?)
I use simple_form for :employments edit and new. The date fields are date selectors.
What about a validates_presence_of with an :unless conditional?
Here's an example:
Validate presence of field only if another field is blank - Rails
Off the top of my head, why not just set the end_date field to nil and treat that value as "currently employed" in your UI?
What is the best way to define a fixed-length SQL column (CHAR(12) for instance) through a Rails migration ?
Why this should not handled by the model is because of the performance of char() vs varchar(), and I'd like to avoid injecting raw SQL in the database.
Edit : I know the :limit modifier, however the field is still varchar (which is bad for performance) and does not allow a minimum size.
If Rails doesn’t understand the column type, it’ll pass it straight through to the database. So if you want a char instead of varchar, just replace:
t.column :token, :string
With:
t.column :token, "char(12)"
Of course, this may or may not make your migrations non-portable to another database.
(credit to http://laurelfan.com/2010/1/26/special-mysql-types-in-rails-migrations)
def self.up
add_column("admin_users", "username", :string, :limit => 25)
end
def self.down
remove_column("admin_users", "username")
end
You can use string type with limit option in your migration file like this:
t.string :name, :limit => 12, :null => false
For a database specific type, we can now use:
t.column(:column_name, 'char(12)')
And for a complete example:
class Foo < ActiveRecord::Migration
def change
create_table :foo do |t|
t.column(:column_name, 'custom_type')
t.timestamps
end
end
end