Insert JSON file into Postgres through Rails - ruby-on-rails

I'm not familiar on how to handle inserting JSON in a Postgres DB in Rails. I saw you can declare a json column type in rails.
I now want to run the command Users.new("Bob", bob) but get the error:
ArgumentError: wrong number of arguments (given 2, expected 0..1)
bob = {
name: "Bob",
occupation: "Coder",
pets: [
name: "Foo",
type: "dog"
]
}
class CreateUsers < ActiveRecord::Migration[5.1]
def change
create_table :users do |t|
t.string :name
t.json :data
end
end
end
In Rails console, I also double checked my table to make sure it's what I expected. So not sure what I'm doing wrong.
User(id: integer, name: string, data: json)

You need to specify the names of the columns. Try this:
Users.new(name: "Bob", data: bob)

I forgot to add the name of the columns I'm passing my values to.
Users.new("Bob", bob)
becomes
Users.new(name: "Bob", data: bob)

Related

ActiveRecord: find_by oblivious to custom getter

ActiveRecord's find_by method seems to be oblivious to custom getters. That is, find_by seems to look at values actually stored in the database and not at what the getters return. Example:
ActiveRecord::Base.establish_connection("sqlite3::memory:")
ActiveRecord::Schema.define do
create_table(:users) { |t| t.string "name" }
end
class User < ActiveRecord::Base
def name; self["name"] || "default" end
end
User.create({ "name" => "foo" })
User.create()
p User.find_by("name" => "foo")
p User.find_by("name" => "default")
p User.last.name
This prints:
#<User id: 1, name: "foo">
nil
"default"
I would have expected the second line not to be nil, but instead to show the second record added. What I am seeing may be the expected behavior of find_by (I don't know). However, what method could I use instead of find_by, which will use the value returned by the getters?
ActiveRecord is an interface to a database - all queries are converted to SQL. User.find_by("name" => "foo") executes SELECT * FROM users WHERE name = 'foo', then wraps results in a Ruby User object.
Your database is not aware of getters. To use a getter, you have to have a Ruby object first - which means you would need to retrieve all objects from your table, then use .select to filter the results in Ruby.
In addition to Amandan answer.
If you want to have defaults that are used by ActiveRecord finders, you need to set a default value on DB column:
ActiveRecord::Schema.define do
create_table(:users) { |t| t.string "name", default: 'default' }
end

Rails - Seeding the database with nil instead of values

I have created an app in Rails 4.2.3
I am trying to seed the database but after I do rake db:migrate I am getting nil values in the field of the database table. The only piece of code I have in my seeds.rb is:
User.create![{username: "test", password_digest: "test"}]
In my schema.rb I have:
create_table "users", force: :cascade do |t|
t.string "username"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
My User model is:
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy
has_many :todo_lists, dependent: :destroy
has_many :todo_items, through: :todo_lists, source: :todo_items
end
There are other people that experienced the same problem like here and here but I don't think that the solutions apply to my case as I do not have any attr_accessor in any of my models.
Anyone knows why that might be happening?
brace-types matter...
use the following instead
User.create!( username: "test", password_digest: "test" )
This tells rails to create a single user, using the hash that contains username "test" and password "test"
what you have is telling rails to create a user with an array [] that contains a hash {}... but create is not expecting an array of hashes - it's expecting just a single hash.
EDIT: re: using []
That's because when you don't use () but do use [], then ruby has to guess what you mean...
are you passing an array to a method or are you calling the array-indexing method on a variable? (or hash-indexing if it's not an integer in the brackets)?
If you put the [] right next to the word create! (eg User.create![]) ruby will probably interpret that as the latter... eg it's looking for an variable on the User class called create!... and then tries to look for a key of that hash called {username: "test", password_digest: "test"}, {username: "test2", password_digest: "test2"} and then it gets confused because when you use the array-indexing method [] you should pass it only one key to find, not two ({username: "test", password_digest: "test"} is the first and {username: "test2", password_digest: "test2"} is the second)... thus giving you the error that you're trying ot pass 2 arguments instead of one... but also just using the wrong interpretation to begin with.
neither of which is what you want...
If you leave a space between the [] and create! then ruby is more likely to interpret the [] as a parameter being passed to a method... and then it triggers a different error.
To be unambiguous - always use a space... or use parens () when passing an argument that could be ambiguous in this way.

How to order by descending value in controller?

I am trying to show the list of jobs ordered by median_salary by descending order. So far, it seems to only take into account the first number of median_salary. So something like 900 is listed above 1000, even though the value of 1000 > 900.
homes_controller.rb:
def index
nyc_highest = Highestpaidjob.where("city = ?", "NYC")
#nyc_highest = nyc_highest.order("median_salary DESC")
end
index.html.erb:
<%= #nyc_highest.inspect %>
returns:
#<ActiveRecord::Relation [#<Highestpaidjob id: 11, job: "Architect, City Planner", median_salary: "95928.48", count: 237, margin_of_error: "", city: "NYC", state: "New York", created_at: "2016-07-25 18:17:17", updated_at: "2016-07-25 18:17:17">, #<Highestpaidjob id: 7, job: "Medical", median_salary: "170507.69", count: 128, margin_of_error: "", city: "NYC", state: "New York", created_at: "2016-07-25 18:09:30", updated_at: "2016-07-25 18:09:30">]>
It is listing 95928.48 as higher than 170507.69. Am I missing a condition?
I've looked at Best way to implement sort asc or desc in rails and it seemed to suggest the way I am currently writing the sort.
It's because your median_salary database field is string and it's sorted as string. You need to cast it to integer in order clause, or create a migration, which will change field datatype.
Difference between strings being sorting and floats being sorted:
irb(main):001:0> ["95928.48", "170507.69"].sort
=> ["170507.69", "95928.48"]
irb(main):002:0> [95928.48, 170507.69].sort
=> [95928.48, 170507.69]
In postgres your order clause should looks like this:
#nyc_highest = nyc_highest.order("CAST(median_salary as FLOAT) DESC")
As #teksisto said, you should change the median_salary for float or some type that accepts decimals. Also, I would suggest to create a scope on your model, something like
scope :nyc_highest, -> { where("city = ?", "NYC").order("median_salary DESC") }
on your Highestpaidjob model. Then, you just call Highestpaidjob.nyc_highest in any place of your application you like.
For changing the median_salary data type:
rails g migration ChangeMedianSalaryType
then edit your migration file:
class ChangeMedianSalaryType < ActiveRecord::Migration
def up
change_column :highestpaidjobs, :median_salary, :float
end
def down
change_column :highestpaidjobs, :median_slary, :string
end
end

How to store enum as string to database in rails

How do I create a migration in ruby where the default is a string rather than an Integer, I want to store enum into the database, but I do not want to store it as Integer, because then it does not make sense to another application that wants to use the same table. How do I do default: "female" instead of default:0
class AddSexToUsers < ActiveRecord::Migration
def change
add_column :users, :sex, :integer, default: 0
end
end
class User < ActiveRecord::Base
enum sex: [:female, :male]
has_secure_password
end
I
Reading the enum documentation, you can see Rails use the value index of the Array explained as:
Note that when an Array is used, the implicit mapping from the values to database integers is derived from the order the values appear in the array.
But it is also stated that you can use a Hash:
it's also possible to explicitly map the relation between attribute and database integer with a Hash.
With the example:
class Conversation < ActiveRecord::Base
enum status: { active: 0, archived: 1 }
end
So I tested using Rails 4.2.4 and sqlite3 and created an User class with a string type for sex type and a Hash in the enum with string values(I am using fem and mal values to differ from female and male):
Migration:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :sex, default: 'fem'
end
end
end
Model:
class User < ActiveRecord::Base
enum sex: { female: 'fem', male: 'mal' }
end
And in console:
u = User.new
#=> #<User id: nil, sex: "fem">
u.male?
#=> false
u.female?
#=> true
u.sex
#=> "female"
u[:sex]
#=> "fem"
u.male!
# INSERT transaction...
u.sex
#=> "male"
u[:sex]
#=> "mal"
I normally do the following:
# in the migration in db/migrate/…
def self.up
add_column :works, :status, :string, null: false, default: 'offering'
end
# in app/models/work.rb
class Work < ApplicationRecord
ALL_STATES = %w[canceled offering running payment rating done].freeze
enum status: ALL_STATES.zip(ALL_STATES).to_h
end
By using a hash as argument for enum (see docs) this stores strings in the database. At the same time this still allows you to use all the cool Rails helper methods:
w = Work.new
#=> #<Work id: nil, status: "offering">
w.rating?
#=> false
w.offering?
#=> true
w.status
#=> "offering"
w[:status]
#=> "offering"
w.done!
# INSERT transaction...
w.status
#=> "done"
w[:status]
#=> "done"
Update for one-liner:
I overlooked completely we've got index_by since Rails 1.2.6. This makes the solution a one-liner even:
enum status: %w[canceled offering running payment rating done].index_by(&:to_sym)
Alternatively we've got index_with since Rails 6.0.0:
enum status: %i[canceled offering running payment rating done].index_with(&:to_s)
enum in Rails and ENUM type in MySQL are 2 different things.
enum in Rails is just a wrapper around your integer column so it's easier for you to use strings in queries, rather than integers. But on database level it's all converted to integers (automatically by Rails), since that's the type of the column.
ENUM type in MySQL is vendor-specific column type (for example, SQLite doesn't support it, but PostgreSQL does). In MySQL :
An ENUM is a string object with a value chosen from a list of permitted values that are enumerated explicitly in the column specification at table creation time.
CREATE TABLE shirts (
name VARCHAR(40),
size ENUM('x-small', 'small', 'medium', 'large', 'x-large')
);
INSERT INTO shirts (name, size) VALUES ('dress shirt','large'), ('t-shirt','medium'),
('polo shirt','small');
SELECT name, size FROM shirts WHERE size = 'medium';
+---------+--------+
| name | size |
+---------+--------+
| t-shirt | medium |
+---------+--------+
For the migration, you need to do this:
class AddSexToUsers < ActiveRecord::Migration
def change
add_column :users, :sex, "ENUM('female', 'male') DEFAULT 'female'"
end
end
Take a look at this Gist, Rails doesn't provide it out of the box so you have to use a concern:
https://gist.github.com/mani47/86096220ccd06fe46f0c09306e9d382d
There's steps to add enum as string to model Company
bin/rails g migration AddStatusToCompanies status
class AddStatusToCompanies < ActiveRecord::Migration[7.0]
def change
add_column :companies, :status, :string, null: false, default: 'claimed'
add_index :companies, :status
end
end
bin/rails db:migrate
Values are strings (symbols not working)
add Default
add Prefix
enum status: {
claimed: 'claimed',
unverified: 'unverified',
verified: 'verified',
}, default: 'claimed'
Add validation (or will raise sql exception)
validates :status, inclusion: { in: statuses.keys }, allow_nil: true
To my knowledge it is not possible with standard Rails enum. Look at https://github.com/lwe/simple_enum, it is more functionally rich, and also allows storing of enum values as strings to DB (column type string, i.e. varchar in terms of DB).

Can't access uuid field in rails migration

I have a table called 'products' (model is Product) and I can't access the :uuid attribute when running in migration context. The migration itself does not change the structure but accesses and creates new objects to populate the DB.
This is a snippet of the schema.rb prior to the migration:
create_table "products", force: :cascade do |t|
t.string "title"
t.string "description"
t.string "price"
t.uuid "uuid"
end
The Product object is defined as follows:
class Product < ActiveRecord::Base
end
Now when running in rails console / code this works fine:
p = Product.create!(:uuid => xxx)
puts p.uuid # => xxx
puts p.inspect # => Product(title: string, description: string, price: string, uuid: uuid)
However while running in the migration context - the same code raises an exception:
p = Product.create!(:uuid => xxx)
puts p.uuid # => undefined method `uuid' for <Product:0x007fea6d7aa058>/opt/rubies/2.2.0/lib/ruby/gems/2.2.0/gems/activemodel-4.2.3/lib/active_model/attribute_methods.rb:433
puts p.inspect # => Product(title: string, description: string, price: string)
The uuid attribute is missing! What's wrong?
put
Product.reset_column_information
before your Product.create line.
The schema of the model is generally refreshed after the migration. Therefore, even if the uuid field is created, the model doesn't have knowledge of it yet.
You can force a refresh by using
Product.reset_column_information
However, the issue in your code reveals you are probably using the migration feature to create records inside the migration itself. This is generally not recommended as the migrations are designed to change the schema of your database, not the data.
You should use create a specific rake task to modify the data and run the task after the migration is completed, for example from the console.

Resources