Rails 5.1
My migration file:
class CreateFwExports < ActiveRecord::Migration[5.1]
def change
create_table :fw_exports, id: :string do |t|
t.string :screen_name, index: true, limit: 16
t.string :full_name, limit: 21
t.string :location
t.timestamps
end
end
end
In my helper file, I have the following method:
def process_spreadsheet(number_of_rows, spreadsheet)
for i in 1..number_of_rows do
fw_export_record = FwExport.new(
:screen_name => spreadsheet[i][0].to_s,
:full_name => spreadsheet[i][1].to_s,
:location => spreadsheet[i][2].to_s,
)
fw_export_record.save
end
end
What this method does, is receive a spreadsheet CSV object, and iterates through the data, trying to save each row to the fw_exports table.
The first data row is:
xxxxxxxx,xxxxxxxxxx,"Nottingham, England"
I am getting the following error message:
ActiveRecord::AssociationTypeMismatch (Location(#38400060) expected, got "Nottingham, England" which is an instance of String(#10657520)):
app/helpers/fw_exports_helper.rb:21:in `block in process_spreadsheet'
app/helpers/fw_exports_helper.rb:20:in `process_spreadsheet'
app/controllers/fw_exports_controller.rb:82:in `process_parsed_spreadsheet'
When I looked at the actual MySQL table, here's what I got:
id Primary varchar(255) utf8mb4_unicode_ci No None
screen_name varchar(16) utf8mb4_unicode_ci Yes NULL
full_name varchar(21) utf8mb4_unicode_ci Yes NULL
location varchar(255) utf8mb4_unicode_ci Yes NULL
From the controller:
def fw_export_params
params.require(:fw_export).permit(:screen_name, :full_name, :location)
end
id is generated through a method defined in the concerns section
Any idea why I'm getting the error message?
Edit:
In my fw_exports.rb model, I had the following:
has_one :location
I have a locations table (and model), with the following fields:
t.string :fw_exports_id, index: true
t.string :city
t.string :state
t.string :country
When I commented out, the line in the fw_exports.rb model:
# has_one :location
I stopped getting the above mentioned error, and instead, I am now getting the following error:
NoMethodError (undefined method `each' for "0":String):
app/helpers/fw_exports_helper.rb:21:in `block in process_spreadsheet'
app/helpers/fw_exports_helper.rb:20:in `process_spreadsheet'
app/controllers/fw_exports_controller.rb:82:in `process_parsed_spreadsheet'
Same spot in code, different message.
Add |i| after the do
for i in 1..number_of_rows do |i|
Edit after response in comment:
You don't show the model but probably you have a relationship called location that is conflicting with the field.
As you have:
class FwExport < ApplicationRecord
has_one :location
and assuming that:
class Location < ApplicationRecord
belongs_to :fw_export
so you cannot define :location as a string column in CreateFwExports migration.
First you need to write another migration to remove the column from :fw_exports table:
class RemoveColumnFromFwExports < ActiveRecord::Migration[5.1]
def change
remove_column :fw_exports, :location, :string
end
end
Now rewrite the helper method that would parse the location string from csv into a Location instance and assign it into the FwExport instance:
def process_spreadsheet(number_of_rows, spreadsheet)
1.upto(number_of_rows) do |i|
fw_export_record = FwExport.new(
screen_name: spreadsheet[i][0].to_s,
full_name: spreadsheet[i][1].to_s,
)
fw_export_record.save
# now create the location and associate it to fw_export_record
location = find_or_create_location(spreadsheet[i][2].to_s)
location.fw_exports_id = fw_export_record.id
location.save
end
end
private
def find_or_create_location(s)
city, country = s.split(',').map(&:strip)
Location.find_or_create_by!(city: city, country: country)
end
Related
So, I'm building an app where I have a backend written in Rails and a client written in Vue with Amplify. My database is MySQL and I'm using AWS AppSync with a GraphQL as data source (pointing to my database).
The AWS Amplify has a framework that allows me to generate the schemas based on the table names and columns with one simple command: amplify api add-graphql-datasource. But because I'm using rails migrations, my database is using Rails conventions: pluralized tables with snake cased columns.
Now, the problem with that is the GraphQL schemas are all ugly and not using the correct conventions (singular names for the types and inputs, with camel cased props). Example:
My backend has the following migration:
class CreatePosts < ActiveRecord::Migration[6.0]
def change
create_table :posts do |t|
t.belongs_to :site, null: false
t.string :title
t.string :url
t.text :body
t.timestamps
end
end
end
And the schema generated for this is:
type posts {
id: Int!
site_id: Int!
title: String
url: String
body: String
created_at: AWSDateTime!
updated_at: AWSDateTime!
}
type Query {
getPosts(id: Int!): posts
listPostss: [posts]
// ...
}
schema {
query: Query
// ...
}
Not to mention this:
input CreatepostsInput {
id: Int!
site_id: Int!
title: String
url: String
body: String
created_at: AWSDateTime!
updated_at: AWSDateTime!
}
So, AWS Amplify is new, it's not mature as Rails, and on top of that I didn't find any adapter or transformer to handle the problem in the client... my hope is to find a way to handle it on Rails.
I need to be able to completely change the Rails conventions without breaking anything: migrations, associations, how to manage associations (create_xxx, build_xxx).
This app is really new, so I can recreate all the migrations from scratch.
Thanks
I can see some things you can do:
Tables:
In your migration, you can change the table name, but now you need to let your model know what's the table name with self.table_name.
# migration
class CreatePosts < ActiveRecord::Migration[6.0]
def change
create_table :post do |t| # singular 'post'
...
end
end
end
#model
class Post
self.table_name = "post" # i think you can also use a symbol :post
end
Attributes:
You need to avoid using Rails migration methods that follow Rails conventions like t.belongs_to or t.references or t.timestamps.
# migration
class CreatePosts < ActiveRecord::Migration[6.0]
def change
create_table :post do |t|
# do not use this below
# t.belongs_to :site, null: false
t.bigint :siteId, null: false
t.string :title
t.string :url
t.text :body
# do not use this below
# t.timestamps
t.datetime :createdAt, default: ->{'CURRENT_TIMESTAMP'}
t.datetime :updatedAt, default: ->{'CURRENT_TIMESTAMP'}
end
end
end
Relationships:
You also need to update your relationships in your models
class Post
belongs_to :site, foreign_key: 'siteId'
end
More information can be found in the Rails API. Make sure you check the documenation for other relationship methods.
Timestamps:
Since timestamps columns (created_at, updated_at) are not the ones expected by ActiveRecord anymore, you might need to override the ActiveRecord::Timestamp module to have them continue working as you would expect. One of the easiest option is to update your ApplicationRecord or a single model class with the following:
class ApplicationRecord # or class Post
before_create :set_timestamps
before_save :set_timestamps
private
def set_timestamps
self.createdAt = DateTime.current if self.new_record?
self.updatedAt = DateTime.now
end
end
Or this other option taken from https://stackoverflow.com/a/52276865/1845602
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
class << self
private
def timestamp_attributes_for_create
super << 'my_created_at_column'
end
def timestamp_attributes_for_update
super << 'my_updated_at_column'
end
end
end
Auto generated Inputs:
I might be wrong here, but it seems the table name is being injected into the input name like Create<table>Input, so if that's the case, you can name your table Post instead of post.
input CreatepostsInput {
}
I have want to create and then update my subclass, but after creating it, it loses its primary_key, making any update impossible.
My (simplified) setup is the following:
Rails 4.2.9
ruby-2.1.10
Models:
class Contact < ActiveRecord::Base
attr_accessible :firstname,
:lastname
end
class SimpleContact < Contact
self.primary_key = 'id'
end
Both models sharing the contacts table (STI).
Controller:
module Public
class FilmEntriesController < PublicController
# ...
def create
logger.debug "SIM PK #{SimpleContact.primary_key}" # id
contact = SimpleContact.create()
logger.debug "SIM PK after create #{SimpleContact.primary_key}" # nil
contact.update_attributes({"firstname"=>"Moe", "lastname"=>"Test"})
end
end
The contacts table was created with this migration:
class CreateContacts < ActiveRecord::Migration
def change
create_table :contacts do |t|
t.string :firstname # | kontakt_vorname | varchar(64)
t.string :lastname # | kontakt_nachname | varchar(64)
t.timestamps
end
end
end
The full error msg is then
ActiveRecord::StatementInvalid in Public::FilmEntriesController#create
Mysql2::Error: Unknown column 'contacts.' in 'where clause': UPDATE
`contacts` SET `firstname` = 'Moe', `lastname` = 'Test', `updated_at`
= '2017-09-07 11:09:39' WHERE `contacts`.`` = 21964
Thank you for any hints!
To use STI you need a type column in the respective table in the database. Just add to your migration:
t.string "type", default: "Contact", null: false
and remove
self.primary_key = 'id'
from your model.
I guess you are having problems because you set the SimpleContact model to NOT generate a ID by itself. I'm deducting that because you are using this line of code in SimpleContact:
self.primary_key = 'id'
and because none id is being generate.
You can check whether you set or not in you migration file for the SimpleContact table.
If that is true, one way to solve it could be use this line of code in your SimpleContact model:
before_create { self.id = generator_id() }
Where the generator_id method could be any value of id you want. That way your current code would work.
Another way to solve it would be set the SimpleContact table to generate the id by itself. You can google it to see how it is done.
Good luck!
I am having an issue trying to do a bulk reverse geocode using the geocoder rails gem: https://github.com/alexreisner/geocoder
I have the following models:
class School < ActiveRecord::Base
reverse_geocoded_by :latitude, :longitude
has_one :address
end
and
class Address < ActiveRecord::Base
belongs_to :school
end
And the following migrations:
class CreateSchools < ActiveRecord::Migration
def change
create_table :schools do |t|
t.string :name
t.integer :address_id
t.timestamps null: false
end
end
end
and
class CreateAddresses < ActiveRecord::Migration
def change
create_table :addresses do |t|
t.string :line_1
t.string :line_2
t.string :line_3
t.string :city
t.string :region
t.string :country
t.string :code
t.timestamps null: false
end
end
end
and when I run the following line:
rake geocode:all CLASS=School REVERSE=true SLEEP=0.5
I get this error:
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column schools.address does not exist
LINE 1: SELECT "schools".* FROM "schools" WHERE (schools.address IS...
^
: SELECT "schools".* FROM "schools" WHERE (schools.address IS NULL) ORDER BY "schools"."id" ASC LIMIT 1000
I know the readme says this:
"For geocoding your model must provide a method that returns an
address. This can be a single attribute, but it can also be a method
that returns a string assembled from different attributes (eg: city,
state, and country)."
I took that to mean I needed either a method on the School model or the attribute on the school table and I opted for the latter but I'm not sure what I'm missing.
Thanks!
The problem is that the reverse-geocoding rake task starts by loading all the records with no address column yet. It uses this scope:
scope :not_reverse_geocoded, lambda {
where("#{table_name}.#{geocoder_options[:fetched_address]} IS NULL")
}
The problem is you don't have any column on schools you could use. Instead, you should move the reverse_geocoded_by declaration to the Address class. You will also need to either add an addresses.address column or do something like this:
reverse_geocoded_by :latitude, :longitude, fetched_address: :line_1
Also you don't seem to have columns for latitude and longitude. And of course those should be on Address too, not School. After all, if a school can have several addresses, which one is its lonlat?
I recently added new columns to my database (sqlite) for Media and it shows that the columns are inserted, but the new columns will not update on Medium.new
My original database:
class CreateMedia < ActiveRecord::Migration
def change
create_table :media do |t|
t.string :name
t.string :location
t.timestamps
end
end
end
These columns update on Media.new
class AddMetaToMedia < ActiveRecord::Migration
def change
add_column :media, :ext, :string
add_column :media, :l_mod, :string
add_column :media, :d_create, :string
end
end
and I am calling
Medium.new(name: f, location: str, ext: ex)
ext will not update to ex = File.extname(f), which I know has a value through print statements/console. Am I calling Medium.new wrong? Why is it updating name and location but not the new columns?
edit: Here is my model, I've tried with and without attr_accessible/attr_accesor
class Medium < ActiveRecord::Base
attr_accessor :ext, :d_create, :l_mod
end
Mostly for those who find this later...may also need to whitelist new params in interested controllers
attr_accessor :ext, :d_create, :l_mod remove this line from your model and try again.
Now you have these attributes in DB so Rails will do this job automatically
I'm a beginner in Rails, and I'm having trouble inserting rows into the database using Rails's migration.
class Actions < ActiveRecord::Migration
def up
create_table :actions do |t|
t.integer :channel_id
t.string :name
t.text :description
t.integer :weight
t.timestamps
end
add_index :actions, :channel_id
Actions.create :name => 'name', :description => '', :weight => 1, :channel_id => 1
end
Running this code results in:
== Actions: migrating ========================================================
-- create_table(:actions)
-> 0.0076s
-- add_index(:actions, :channel_id)
-> 0.0036s
-- create({:name=>"name", :description=>"", :weight=>1, :channel_id=>1})
rake aborted!
An error has occurred, this and all later migrations canceled:
SQLite3::SQLException: unrecognized token: "{": {:name=>"name", :description=>"", :weight=>1, :channel_id=>1}
The Action model:
class Actions < ActiveRecord::Base
belongs_to :channels
attr_accessible :name, :description, :weight, :channel_id
end
I don't know where the curly brackets come from and why they cause an exception. Who can help me solving this problem?
Uh oh, it seems that your migration class name is the same as the name of the model you're trying to access (Actions). Because of this, instead of the model class, the create method will be called on the migration class, which probably tries to create a table using your hash, or something. That's why you're getting that error message.
Rename your migration class (and also its file for the sake of consistency) and it should run fine:
class CreateActions < ActiveRecord::Migration