Understanding rails migration statement (:null => false) - ruby-on-rails-3

I am trying to understand the following statement, it is from a rails migration file:
x.datetime "new", :null => false
x.datetime "update", :null => false
I understand the the first part of both statements (everything before the comma) but I am unsure on the null portion
:null => false
Is this basically saying "if it does not exist, then it is false?" The logic just seems a bit strange, any clarification on this would be greatly helpful.

:null => false in a Rails migration tells your database not to accept NULL values. It can be used with :default => 0 to tell your database to use '0' as the default value (a) when NULL or nothing is specified in a query or (b) when creating or updating an object. (Remember, '0' and NULL are not the same things.)

Firstly, rather than use x I'd use the standard t variable that is used in migrations.
Now, inside migration files the t object in create_table is actually of type ActiveRecord::ConnectionAdapters::TableDefinition.
And,
t.datetime "new", :null => false
t.datetime "update", :null => false
actually translates to
t.column("new", :datetime, { :null => false })
t.column("update", :datetime, { :null => false })
where the last argument is the options argument of the column method.
According to the documentation one of these options is :null which allows or disallows NULL values in the column.
So, in summary :null => false would mean "Do not allow NULL values in the new or update column".

Edit: I had taken the question to be about syntax and translation since it originally mentioned CoffeeScript. For purpose, refer to Peter Bloom's answer.
I am not sure what the :null => false means exactly.
The => operator is a key/value separator in Ruby, defining a Hash with a :null key set to false. It's similar to : for an Object literal in CoffeeScript/JavaScript -- { null: false }.
When used in an argument list, it's one option for allowing/imitating named arguments in Ruby.
The other main difference is that CoffeeScript/JavaScript use Strings for keys while Ruby typically uses Symbols -- "null" (cs/js) vs. :null (rb).
So, the syntactic equivalent in CoffeeScript would be:
x.datetime "new", null: false
x.datetime "update", null: false
In JavaScript, that's:
x.datetime("new", { null: false });
x.datetime("update", { null: false });

Related

Rails 5.2 Error Changing or Removing Table Column (SQLite3::ConstraintException: FOREIGN KEY constraint failed: DROP TABLE)

I'm trying to accomplish the fairly simple feat of changing the default value for one of the columns from my Blog table. I have the following migration:
class UpdateBlogFields < ActiveRecord::Migration[5.2]
def change
change_column :blogs, :freebie_type, :string, default: "None"
end
end
Fairly simple, but I'm getting the following error when I run rake db:migrate:
StandardError: An error has occurred, this and all later migrations canceled:
SQLite3::ConstraintException: FOREIGN KEY constraint failed: DROP TABLE "blogs"
I get this error any time I try to change or remove a column, but not when adding one.
My schema looks like this:
create_table "blogs", force: :cascade do |t|
t.string "title"
t.string "teaser"
t.text "body"
t.string "category", default: "General"
t.string "linked_module"
t.boolean "published", default: false
t.datetime "published_on"
t.integer "user_id"
t.integer "image_id"
t.integer "pdf_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "slug"
t.string "cta_read_more", default: "Read More"
t.string "cta_pdf", default: "Get My Free PDF"
t.string "cta_video", default: "Watch the Video"
t.string "convertkit_data_form_toggle"
t.string "convertkit_href"
t.integer "pin_image_id"
t.string "data_pin_description"
t.string "freebie_filename"
t.string "video_link"
t.string "freebie_type", default: "File"
t.string "freebie_description"
t.integer "comments_count"
t.integer "subcategory_id"
t.boolean "affiliate_links", default: true
t.boolean "approved", default: false
t.boolean "submitted", default: false
t.index ["image_id"], name: "index_blogs_on_image_id"
t.index ["pdf_id"], name: "index_blogs_on_pdf_id"
t.index ["pin_image_id"], name: "index_blogs_on_pin_image_id"
t.index ["slug"], name: "index_blogs_on_slug", unique: true
t.index ["subcategory_id"], name: "index_blogs_on_subcategory_id"
t.index ["user_id"], name: "index_blogs_on_user_id"
end
It seems that this might be an SQLite thing, because this post and this one seem to be having a similar problem. However, neither post involves an actual answer. Has anyone successfully gotten rid of this?
UPDATE:
A new column default can be added via Rails without having to use the database. In the Blog model, we can use ActiveRecord::Attributes::ClassMethods::attribute to redefine the default value for freebie_type:
attribute :freebie_type, :string, default: 'None'
This will change the default at the business logic level. Therefore, it is dependent on using ActiveRecord to be recognized. Manipulation of the database via SQL will still use the old default. To update the default in all cases see the original answer below.
ORIGINAL ANSWER:
Unfortunately, ALTER COLUMN is only minimally supported by SQLite. The work around it to create a new table, copy the information to it, drop the old table, and finally rename the new table. This is what Rails is attempting to do, but without first disabling the foreign key constraints. The foreign key relations to user_id, image_id, and pdf_id are preventing the table deletion.
You will need to do the update manually, either with SQL (preferred) or ActiveRecord::Base.connection. You can see the process here under 'Modify column in table'. You can find all the options available for columns in the SQLite Create Table Documentation.
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
ALTER TABLE table1 RENAME TO _table1_old;
CREATE TABLE table1 (
( column1 datatype [ NULL | NOT NULL ] DEFAULT (<MY_VALUE>),
column2 datatype [ NULL | NOT NULL ] DEFAULT (<MY_VALUE>),
...
);
INSERT INTO table1 (column1, column2, ... column_n)
SELECT column1, column2, ... column_n
FROM _table1_old;
COMMIT;
PRAGMA foreign_keys=on;
Be certain that you have all the columns set up the way you want as you will not be able to fix it after the table is created! Going forward I would highly recommend setting up either a PostgreSQL or MySQL2 database. They are much more powerful and will be much easier to modify and maintain.
You can add an initializer to monkey patch the sqlite adapter to make it work with rails 5, just make sure you have sqlite >= 3.8, with this code:
blog/config/initializers/sqlite3_disable_referential_to_rails_5.rb
Content:
require 'active_record/connection_adapters/sqlite3_adapter'
module ActiveRecord
module ConnectionAdapters
class SQLite3Adapter < AbstractAdapter
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity # :nodoc:
old_foreign_keys = query_value("PRAGMA foreign_keys")
old_defer_foreign_keys = query_value("PRAGMA defer_foreign_keys")
begin
execute("PRAGMA defer_foreign_keys = ON")
execute("PRAGMA foreign_keys = OFF")
yield
ensure
execute("PRAGMA defer_foreign_keys = #{old_defer_foreign_keys}")
execute("PRAGMA foreign_keys = #{old_foreign_keys}")
end
end
def insert_fixtures_set(fixture_set, tables_to_delete = [])
disable_referential_integrity do
transaction(requires_new: true) do
tables_to_delete.each {|table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete"}
fixture_set.each do |table_name, rows|
rows.each {|row| insert_fixture(row, table_name)}
end
end
end
end
private
def alter_table(table_name, options = {})
altered_table_name = "a#{table_name}"
caller = lambda {|definition| yield definition if block_given?}
transaction do
disable_referential_integrity do
move_table(table_name, altered_table_name,
options.merge(temporary: true))
move_table(altered_table_name, table_name, &caller)
end
end
end
end
end
end
Here is the gist: https://gist.github.com/javier-menendez/3cfa71452229f8125865a3247fa03d51
first u need to migrate your database rake db:migrate after type this line in your console rails g migration Removevideo_linkFromblogs video_link:string

Rails Seed Data not loading properly after migration

I loaded some seed data in using the code below, and it worked fine. Then, I needed to add two more columns, and I did so using the following steps, but it's not attaching the two new columns seed data to the table, what am I doing wrong?
Steps:
Add two columns using migration
Make them attr_accessible in the model
Replace old CSV file with new CSV file
Change the seed.rb file to plug in the new data
Run rake db:seed
Seed.rb
require 'csv'
Model.delete_all
CSV.foreach("#{Rails.root}/lib/data/model.csv") do |row|
Model.create!(:model_number => row[0], :areq => row[1], :length => row[2], :width => row[3], :depth => row[4], :material => row[5], :frame => row[6], :edge => row[7], :tubes => row[8], :tube_length => row[9])
end
Schema.rb
create_table "models", :force => true do |t|
t.string "model_number"
t.float "areq"
t.float "length"
t.float "width"
t.float "depth"
t.string "material"
t.string "frame"
t.float "edge"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.float "tubes"
t.float "tube_length"
end
Cant say this may work 100%
Try Model.reset_column_information above the seed file once
this should refresh the columns information in table.
require 'csv'
Model.reset_column_information
Model.delete_all
#......
just a guess check if the proper model.csv is getting loaded and have column 8 and 9 means have in all 10 columns.

Problems with default values for updated_at and created_at with SQL migrations in Rails?

I'm writing migrations in Rails 3.2 using SQL (i.e. execute) to enforce application logic (foreign keys, defaults, checks, triggers) at the database level. The reason is that I want consistent database interactions in case I need to use the same database from different applications (i.e. not Rails) or mass import data directly. Everything seems to work splendidly except for default values for one model (see below) .
For example created_at would include NOT NULL DEFAULT current_timestamp. This is all nice if I create a record in the database, but for some reason schema.rb doesn't detect the default values, but correctly identifies the NOT NULL constraint. The consequence is that I can't save an instance in the database using Model.create or Model.new (e.g. BibliographicItem.create(title: "Foo")), because created_at and updated_at end up being nil which violates the null: false constraint in schema.rb.
The offending table in schema.rb:
create_table "bibliographic_items", :id => false, :force => true do |t|
t.string "uuid", :limit => nil, :null => false
t.string "title", :limit => nil, :null => false
t.integer "publication_year"
t.string "type", :limit => nil, :null => false
t.text "description"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
Its model:
class BibliographicItem < ActiveRecord::Base
include Extensions::UUID
attr_accessible :title, :publication_year, :description
has_many :authorships
has_many :authors, through: :authorships, order: "role DESC, author_order DESC NULLS LAST"
validates :title, presence: true, length: { maximum: 500 }
validates :publication_year, numericality: { only_integer: true,
less_than_or_equal_to: Date.today.year() }
end
The table creation in the execute statement:
CREATE TABLE bibliographic_items (
uuid uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
title varchar NOT NULL,
publication_year int CHECK (publication_year <= left(now()::text, 4)::int),
type varchar NOT NULL REFERENCES bibliographic_item_types ON UPDATE CASCADE ON DELETE RESTRICT,
description text,
created_at timestamp without time zone NOT NULL DEFAULT current_timestamp,
updated_at timestamp without time zone NOT NULL DEFAULT current_timestamp,
CHECK (updated_at >= created_at)
);
Why do .create and .new not assign values for created_at and updated_at? For all my other models, with similar (but simpler definitions) there is no issue.
I found the cause of my problems, which as it happens, has nothing to do with SQL migrations, created_at or schema.rb, but is caused by failing the validation validates publication_year, numericality: { only_integer: true, less_than_or_equal_to: Date.today.year() } when publication_year is nil.
Bangs head against wall.

Rails Migration to make a column null => true

I had originally created a table with column as
t.string "email", :default => "", :null => false
The requirement has changed and now I need to allow email to be null. How can I write a migration to make :null => true
change_column_null worked perfectly:
change_column_null :users, :email, true
The reverse has a nice option to update existing records (but not set the default) when null is not allowed.
Try:
change_column :table_name, :email, :string, null: true

Is there a gem to manage settings per resource?

I need a gem that can abstract resource setting management. Basically I want something like
#person = Person.find(1)
#person.settings <- this gives a hash of key/value pairs associated with this resource
I also need a way to have "default" settings per Person as well as a way to override those for specific #person. The settings should be persisted in SQL db.
This is an old plugin but it's pretty full featured and I've used it in a number of different projects: has_easy
For Rails3 the generator won't work but you can just create the migration it needs by hand.
class CreateHasEasyThings < ActiveRecord::Migration
def self.up
create_table :has_easy_things do |t|
t.string :model_type, :null => false
t.integer :model_id, :null => false
t.string :context
t.string :name, :null => false
t.string :value
t.timestamps
end
end
def self.down
drop_table :has_easy_things
end
end
Basically the way it works is that you can associate any model you want with this object and it will store preferences, settings or really pretty much anything that can be serialized by Rails.
You define your settings or what have you on the model:
class User < ActiveRecord::Base
has_easy :settings do |p|
p.define :language
p.define :theme
end
has_easy :flags do |f|
f.define :is_admin
f.define :is_spammer
end
end
which dynamically creates a bunch of methods for accessing settings and flags.
u = User.new
u.settings.language = 'en/us'
u.flags.is_admin = true
If you have rails 2 or rails 3, check out Ledermann's rails-settings gem which has a syntax to get all key-value pairs just like you asked for:
son.settings.all
Easy to add settings:
son.settings.key = value
You also get activerecord scopes to search based on settings. And you can set default settings and global (application scope) settings.
For rails 2, you need to use version "~> 1.x" and follow docs: https://github.com/ledermann/rails-settings/blob/c5c34faf1bbe5742b58f6b3acff3874edc6e4bbc/README.md
If you need :before and :after event handling, check https://github.com/exceed/preferential/