PostgreSQL point type in Rails migration - ruby-on-rails

I want to use the point type which there's in PostgreSQL. I've done:
rails g model Test point:point
The resulting migration is:
class CreateTests < ActiveRecord::Migration
def change
create_table :tests do |t|
t.point :point
t.timestamps
end
end
end
When I run:
rake db:migrate
The result is:
== CreateTests: migrating ====================================================
-- create_table(:tests)
rake aborted!
An error has occurred, this and all later migrations canceled:
undefined method `point' for #<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::TableDefinition:0x000000038991a0>/home/i/Dropbox/programming/ruby/rails_pg_for_tests/db/migrate/20140306151700_create_tests.rb:4:in `block in change'
/home/i/.rvm/gems/ruby-1.9.3-p448/gems/activerecord-4.0.2/lib/active_record/connection_adapters/abstract/schema_statements.rb:184:in `create_table'
/home/i/.rvm/gems/ruby-1.9.3-p448/gems/activerecord-4.0.2/lib/active_record/migration.rb:625:in `block in method_missing'
/home/i/.rvm/gems/ruby-1.9.3-p448/gems/activerecord-4.0.2/lib/active_record/migration.rb:597:in `block in say_with_time'
...
May be I need to install PostGIS, but I don't understand what for, if I have point type in PostgreSQL. And I only need to store latitude and longitude without any other options.
How can I use this point type or what is better to use in this case?
Thank you.

You can specify the data type as a string
t.column 'point', 'point'

Rails have support for point for several years. The test cases have been there since rails 4.2 beta.
If you are using rails 4.2 or later. you should not get that error. I have not tested it on 4.2 beta yet. I only test it on rails 5.2 and it works without any additional gem.
https://github.com/rails/rails/blob/24adc20ace69ec0fcf317e0a8d91d112478ba015/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
Usage:
Migration
class CreateAddresses < ActiveRecord::Migration[5.2]
def change
create_table :addresses do |t|
t.point :coordinate
end
end
end
Create a new record
Address.create coordinate: ActiveRecord::Point.new(1, 1)

You receive undefined method 'point' for #<ActiveRecord:... error on migration code
t.point :point
because point method does not exist in ActiveRecord.
ActiveRecord natively supports following data types,
:primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp,
:time, :date, :binary, :boolean
See the official documentation of ActiveRecord for the same.
As suggested by #bridiver, you could choose to specify a type other than the supported datatypes of ActiveRecord(as listed above) with t.column :point, 'point' BUT as quoted in ActiveRecord documentation
this will not be database agnostic and should usually be avoided.
UPDATE
I would recommend utilizing the benefits of activerecord-postgis-adapter gem, as suggested by #euricovidal. It is a geospatial extension for pgsql and it acts as a wrapper around the PostGIS query functions and allows you to write geo-aware migrations.
It will allow you to create a point type and using this your current syntax should work.
Refer PostGIS ActiveRecord Adapter: Creating Spatial Tables for example. You would also need to update the adapter to postgis in the database.yml file for this to work.

if you are using activerecord-postgis-adapter you need change adapter on database.yml to postgis.
https://github.com/dazuma/activerecord-postgis-adapter#installation-and-configuration

You most likely need to edit your config\database.yml to reference postgis as your database adapter
default: &default
adapter: postgis
encoding: unicode
pool: 5
timeout: 5000
host: localhost
development:
<<: *default
database: learning_rails
username: brentpayne
password:
You might also want to edit your migration to specify the :point as geographic so you can calculate distances.
t.point :point, :geographic => true
But the error you are having is identical to mine when I left my adapter set to postgresql. Changing it to postgis fixed my issues.
Reference: https://github.com/rgeo/activerecord-postgis-adapter#installation-and-configuration

If you install georuby the point datatype becomes available.
Gemfile
gem 'pg'
gem 'georuby'
In a migration
def change
create_table :space_table do |t|
t.point :location
t.timestamps
end
end
You can save a point with model_object.location = [-33.233, 123.123]

Related

Rails migration stopped, even though table does not exist

Rails 6
environment: local development on Mac OS
DB server: MySQL
I deleted all the tables from the DB, and the only tables left in the DB are:
schema_migrations
ar_internal_metadata
I made sure that schema_migrations has no data in it and looked into ar_internal_metadata, and that table has a single row in it, with the following values:
key: environment, value: development
I have several migrations, the most recent one, is devise_create_users.rb.
I am trying to run:
rake db:migrate
But I am getting the error message:
=> rake db:migrate
== 20200317184535 DeviseCreateUsers: migrating ================================
-- create_table(:users)
rake aborted!
StandardError: An error has occurred, all later migrations canceled:
Mysql2::Error: Table 'users' already exists
/Users/dev/rails/myapp/db/migrate/20200317184535_devise_create_users.rb:5:in `change'
Caused by:
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'users' already exists
/Users/dev/rails/myapp/db/migrate/20200317184535_devise_create_users.rb:5:in `change'
Caused by:
Mysql2::Error: Table 'users' already exists
/Users/dev/rails/myapp/db/migrate/20200317184535_devise_create_users.rb:5:in `change'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
Process finished with exit code 1
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
t.string :reset_password_token
t.datetime :reset_password_sent_at
t.datetime :remember_created_at
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
end
When I check the DB after this, I still don't see a users table, and the schema_migrations table is still empty. In addition, the DeviseCreateUsers migration is the most recent on, so why is it running first.
Any ideas?
Edit:
Based on a comment to the question, I looked at my database.yml file:
default: &default
host: localhost
database: <%= ENV['RAILS_DB_NAME'] %>
username: <%= ENV['RAILS_DB_USER'] %>
password: <%= ENV['RAILS_DB_PWD'] %>
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
socket: /tmp/mysql.sock
development:
<<: *default
I made that change last night, and I forgot that my local ENV settings, were for a different project, so Rails was picking up the settings for that project, and was indeed correct, in that the users table is already there. The fix was for me to create project specific ENV settings, for my local development environment
When things like this happen it is needed to check the database.yml. As in most cases it happens because of some faulty configuration.
Good thing that just mentioning the database.yml in the comment section of the question helped to find the problem.
Using rails console to make sure there is not any users table, try create a new instance of User User.new. If the object was instanciated, your User table is hidden in somewhere. It is not a problem, see all atributtes previously generated and just migrate new ones. I can see in your devise_create_user migration that you can just replace it with something like addColumnstoUser.rb. This will just add the apropriated columns necessary to let Devise runing well.
Choosing this way, inside your migration:
add_column :table_name, :fieldname, :type, in your case, using just one column as an example, it must be:
def change
add_column :users, :reset_password_token, :string
end.
If you are using version controll, such as git, the migration might not know where you can rollback or go ahead. All the migration and rollbacks must stay on the same branch.
For example:
1) Adding a new branch to handle user migration: git checkout -b generate-user-model, generating user model rails generate model User name:string email:string
and running the migration rails db:migrate will create the users table and will update scheema.rb (check scheema file). Adding it to version controll git add . and `git commit -m "Migrating user model"
2) Moving to a new branch git checkout -b comments-controller, generating a new controller rails generate model Comment. And then migrating it rails db:migrate.
This second migration only teachs scheema.rb how to go ahead and how to rollback on this specifc migration. This last git branch doesn't know nothing about how to rollback the User model, unless you merge a branch inside another git merge generate-user-model.
After many migrations has been used, it's usefull keep using migrations to add or delete tables.
Hence, If I had a table called users and I wanted to set it to handle Devise, I just need to generate a new migration with the columns I'd needed.
Use https://sqlitebrowser.org/ to help you cheking your database tables and columns.

Can't create file field with ActiveStorage in Rails

I'm trying to use Active Storage in Rails 5.2. I found that I should create field with type file in migration, but I have an error:
$ rdm
Running via Spring preloader in process 40193
== 20171217191942 CreateDishes: migrating
=====================================
-- create_table(:dishes)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
undefined method `file' for #<ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition:0x00007fd56e297750>
/Users/alder/Projects/_apps/service_exchange/rails-backend/db/migrate/20171217191942_create_dishes.rb:6:in `block in change'
/Users/alder/Projects/_apps/service_exchange/rails-backend/db/migrate/20171217191942_create_dishes.rb:3:in `change'
-e:1:in `<main>'
Migration:
class CreateDishes < ActiveRecord::Migration[5.2]
def change
create_table :dishes do |t|
t.string :name, index: true
t.string :description
t.file :image
t.timestamps
end
end
end
I was trying to create string field, but it doesn't work.
Couldn't find anything about that in official docs
I have migration for active storage and I passed ok
Instead of a dedicated field that you need to create in your own migration (t.file :image) active storage uses two tables that you setup with rails active_storage:install.
When you setup your storage.yml you should be able to use
has_one_attached :image
within the Dishesmodel without creating a imagecolumn.
You can check this question (ActiveRecord field type) because did not have any type like file if you need to upload a file you can create with type string like t.string

Trouble adding st_point column with activerecord-postgis-adapter

This is a pretty verbatim copy of what I wrote on the project github. Forgive me for cross posting but I was hoping someone here had come across this error:
undefined method `st_point' for #
<ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition:0x0055cdd8f278e8>
This is my Gemfile lock:
activerecord (4.2.7.1)
activerecord-postgis-adapter (3.1.4)
and the migration:
class CreateLocations < ActiveRecord::Migration
def change
enable_extension "postgis"
create_table :locations do |t|
t.st_point :geom, geographic: true, srid: 4326, dimension: 2
t.timestamps
end
end
end
What's wierd is that this works in development mode. It's just when I run the migrations for the test environment that it fails.
After rake db:create RAILS_ENV=test, I have conntected to the test database with psql and run CREATE EXTENSION postgis;.
tl;dr
t.st_point in the migration is undefined, only in the test environment.
The error was "postgres" instead of "postgis" in the ENV["DATABASE_URL"]
That it what others suggested on the thread. I was being stubborn by not checking for this, but it turns out to be the correct cause.

Can't Rails 3 run in MySQL MyISAM mode, without InnoDB?

I have a MySQL server running with InnoDB disabled (for reasons of performance), with this setup I can't seem to be able to use Rails 3 (with the mysql2 adapter).
Here's my test migration:
class CreateTxts < ActiveRecord::Migration
def change
create_table(:txts, :options => 'ENGINE=MyISAM') do |t|
t.timestamps
end
end
end
And here's the error:
>rake db:migrate
rake aborted!
Mysql2::Error: Unknown storage engine 'InnoDB': CREATE TABLE `schema_migrations`
(`version` varchar(255) NOT NULL) ENGINE=InnoDB
Tried the workaround described here, but it doesn't seem to work either (I did modify MysqlAdapter to Mysql2Adapter to match my setup).
Sorry I'm a newb in Rails. Any help will be much appreciated :o
Going to answer my own question. Here's a patch for environment.rb I ended up with that works with the native mysql driver as well as JRuby/JDBC-mysql:
# Load the rails application
require File.expand_path('../application', __FILE__)
# Patch Mysql adapter to default to MyISAM instead of InnoDB
require 'active_record/connection_adapters/mysql_adapter'
module ActiveRecord
module ConnectionAdapters
class MysqlAdapter
def create_table(table_name, options = {}) #:nodoc:
super(table_name, options.reverse_merge(:options => "ENGINE=MyISAM"))
end
end
end
end
# Initialize the rails application
.....
rake db:migrate now succeeds and creates all tables including schema_migrations with TYPE=MyISAM.
Note: For mysql2 adapter, rename mysql_adapter to mysql2_adapter and MysqlAdapter to Mysql2Adapter.
Try creating the table without specifying the type of engine being used like this
class CreateTxts < ActiveRecord::Migration
def change
create_table(:txts) do |t|
t.timestamps
end
end
end
and then in mysql cli, type this
ALTER TABLE txts ENGINE = MYISAM
hope it helps
The error is coming from creating the schema_migrations table (which rails uses to track which migrations have been run) rather than your table. You could create that table yourself (with a single varchar(255) column called version with an index on it).
If you do end up overwriting the create_table method, you need to preserve the method's signature - you're ignoring the block that it yields. I'd try something like
def create_table(name, options={})
super(name, options.merge(...)) {|t| yield t}
end
#rustyx nice one! &here's my slightly tweaked monkey-patch so the engine can be set from within the /config/database.yml configuration:
&Rather than in /config/environment.rb, i put the codes into an initializer, Eg: /config/initializers/mysql2adapter_default_engine.rb.
Either this for brevity:
require 'active_record/connection_adapters/abstract_mysql_adapter'
class ActiveRecord::ConnectionAdapters::Mysql2Adapter < ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
def create_table(table_name, options = {}) #:nodoc:
super(table_name, #config[:engine].nil? ? options : options.reverse_merge(:options => "ENGINE="+#config[:engine]))
end
end
or this for more clarity:
require 'active_record/connection_adapters/abstract_mysql_adapter'
module ActiveRecord
module ConnectionAdapters
class Mysql2Adapter < AbstractMysqlAdapter
def create_table(table_name, options = {}) #:nodoc:
super(table_name, #config[:engine].nil? ? options : options.reverse_merge(:options => "ENGINE="+#config[:engine]))
end
end
end
end
Then in /config/database.yml we can have something like this:
production:
adapter: mysql2
encoding: utf8
database: ooook
username: ooook
password: ooook
host: ooook.tld
port: 3306
pool: 5
timeout: 5000
engine: MyISAM
If no engine is specified then the mysql2 adapter will use the default (InnoDB or whatever).

How do I solve "index unique_schema_migrations already exists" in Rails?

Running rake db:migrate followed by rake test:units yields the following:
rake test:functionals
(in /projects/my_project)
rake aborted!
SQLite3::SQLException: index unique_schema_migrations already exists: CREATE UNIQUE INDEX "unique_schema_migrations" ON "ts_schema_migrations" ("version")
The relevant part of db/schema.rb is as follows:
create_table "ts_schema_migrations", :id => false, :force => true do |t|
t.string "version", :null => false
end
add_index "ts_schema_migrations", ["version"], :name => "unique_schema_migrations", :unique => true
I'm not manually changing this index anywhere, and I'm using Rails' default SQLite3 adapter with a brand new database. (That is, running rm db/*sqlite3 before rake db:migrate doesn't help.)
Is the test:units task perhaps trying to re-load the schema? If so, why? Shouldn't it recognize the schema is already up to date?
In SQLite, index name uniqueness is enforced at the database level. In MySQL, uniqueness is enforced only at the table level. That's why your migrations work in the latter and not the former: you have two indexes with the same name on different tables.
Rename the index, or find and rename the other unique_schema_migrations index, and your migrations should work.
In your database.yml file are your environments setup up to connect to different databases for Development and Test?
IE:
development:
adapter: sqlite3
database: db/dev.sqlite3
timeout: 5000
test:
adapter: sqlite3
database: db/test.sqlite3
timeout: 5000
Try to search if your schema.rb file does not contain other declarations that create an index with the same name: unique_schema_migrations

Resources