Wrong STI column is used during migration - ruby

In a new migration I tried to add a column and populate its values:
def up
add_column :topics, :status, :string
Topic.reset_column_information
Topic.find_each do |t|
if t.ended?
t.status = 'ended'
end
end
Topic.reset_column_information
end
I am not sure why, but after the column is added, during the Topic.find_each the following exception were raised:
The single-table inheritance mechanism failed to locate the subclass: 'other'.
My Topic model did have the following declared:
self.inheritance_column = 'class_name'
So I am not sure why it still try to look up the STI subclass using the type column.

I found that once I removed reset_column_information, the problem is gone.
add_column :topics, :status, :string
# Topic.reset_column_information
Topic.find_each do |t| ...
reset_column_information probably has a bug at removing the STI column setting.

Related

Insert Ecto model with already existing model as an association

I have 2 models, entries:
schema "entries" do
belongs_to :exception, Proj.Exception
field :application, :string
end
And exceptions:
schema "exceptions" do
field :name, :string
end
The migration script:
def change do
create table(:exceptions) do
add :name, :string, null: false
end
create table(:entries) do
add :exception_id, references(:exceptions), null: false
add :application, :string, null: false
end
end
My goal is to store exceptions that happen in another system. I want the project to be able to store each exception in the second table exception if they are not already there and then store the application name and the exception id in the first table entries. There will be 1000s of records in entries and a handful in exceptions.
Assuming entry_params uses this JSON format:
{
exception: "NPE",
application: "SomeApp"
}
the method that should create the entries:
def create(conn, %{"entry" => entry_params}) do
exception = Repo.get_by(Exception, name: entry_params["exception"]) ||
Repo.insert!(%Exception{name: entry_params["exception"]})
changeset =
Entry.changeset(%Entry{}, entry_params)
|> Ecto.Changeset.put_assoc(:exception, exception)
Repo.insert!(changeset)
end
This will print out:
** (ArgumentError) unknown assoc `exception` in `put_assoc`
If I change the entries model to use has_one instead of belongs_to (and I think belongs_to "feels" bad here. An entry does not belong to an exception, it just has an exception) it throws the following:
** (Postgrex.Error) ERROR (not_null_violation): null value in column "exception_id" violates not-null constraint
table: entries
column: exception_id
What I want basically to first create an Exception (if it does not exist) and than create a new Entry of a system error and put the previously begotten Exception in the entry as an association.
What is wrong here?
Typo. belongs_to :exception, Proj.Exception should be belongs_to :exceptions, Proj.Exception
Association. Based on the data model in the question, I think the put_assoc is the wrong way around because in the data schema in the question, an exception has_many entries and an entry belongs_to exceptions. Ecto.Changeset.put_assoc(entries_changeset, :exception, exception) should be Ecto.Changeset.put_assoc(exception_changeset, :entries, entries)
Attempted solution:
entries schema:
schema "entries" do
field :application, :string
belongs_to :exceptions, Proj.Exception, on_replace: :nilify
end
exceptions schema:
schema "exceptions" do
field :name, :string
has_many :entry, Proj.Entry, on_delete: :delete_all, on_replace: :delete
end
migration script:
def change do
create table(:exceptions) do
add :name, :string, null: false
end
create table(:entries) do
add :application, :string, null: false
add :exception_id, references(:exceptions)
end
end
Assuming entry_params uses this JSON format:
{
exception: "NPE",
application: "SomeApp"
}
create or update the exceptions and the associated entries:
def create(conn, %{"entry" => entry_params}) do
new_entry = Entry.changeset(%Entry{}, entry_params)
changeset =
case Repo.get_by(Exception, name: entry_params["exception"]) do
:nil ->
exception = %Exception{name: entry_params["exception"]} |> Repo.insert!
Ecto.Changeset.build_assoc(exception, :entries, [new_entry])
struct ->
changeset = Ecto.Changeset.change(struct)
data = Ecto.Changeset.preload(changeset, :entries) |> Map.get(:model) # Ecto 1.x
# data = Ecto.Changeset.preload(changeset, :entries) |> Map.get(:data) # Ecto 2.0.x
Ecto.Changeset.put_assoc(changeset, :entries, [new_entry | data.entries])
end
Repo.insert!(changeset)
end

Sequel : DRY between schema migration and model validate method

I'm wondering if I miss a way to avoid repeat validation code in my Sequel::Model#validate subclass method since I've already put all constraints into my migration file.
Here's a simple example of what I'm talking about :
Sequel.migration do
change do
create_table :users do
primary_key :id
String :name, :null => false, :unique => true
end
end
end
class User < Sequel::Model
def validate
super
validates_presence :name
validates_unique :name
validates_type String :name
end
end
It seems very painful and errors prone to have to repeat all the constraints in the validate method. Did I miss something or there's no other way to do that ?
Any advice will be appreciated, thanks
Sequel has some nice plugins and extensions.
Sequel::Model.plugin(:auto_validations)
Sequel::Model.plugin(:constraint_validations)
and
DB.extension(:constraint_validations)
auto_validations
The auto_validations plugin automatically sets up three types of
validations for your model columns:
type validations for all columns
not_null validations on NOT NULL columns (optionally, presence
validations)
unique validations on columns or sets of columns with unique indexes
See http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/AutoValidations.html
constraint_validations
The constraint_validations extension is designed to easily create
database constraints inside create_table and alter_table blocks. It
also adds relevant metadata about the constraints to a separate table,
which the constraint_validations model plugin uses to setup automatic
validations.
See http://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/constraint_validations_rb.html
and
http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/ConstraintValidations.html
Your example would look like this
Sequel::Model.plugin(:auto_validations)
Sequel::Model.plugin(:constraint_validations)
Sequel.migration do
up do
extension(:constraint_validations)
create_table :users do
primary_key :id
String :name, :null => false, :unique => true
validate do
presence :name,
name: :presence_name
end
end
end
down do
extension(:constraint_validations)
drop_table(:users)
end
end
class User < Sequel::Model
end
I think, it's normal. Don't worry.

Adding a Hash to an ActiveRecord Hash

I read How to add a Hash object to an ActiveRecord class? Tried but migration fails and followed the format there.
I tried:
class AddTestResponsesToSurveys < ActiveRecord::Migration
def change
add_column :surveys, :responses, :hash
end
end
When I run rake db:migrate, I get an error in my schema.rb file that says:
# Could not dump table "surveys" because of following StandardError
# Unknown type 'hash' for column 'responses'
What am I doing wrong?
generate migration with column type text
class AddTestResponsesToSurveys < ActiveRecord::Migration
def change
add_column :surveys, :responses, :text
end
end
And in your Survey model, add this
serialize :responses, Hash

How can I create a field using the id from the same row in ActiveRecord Ruby

-----UPDATE-----
Well, seems that the problem was in last.id. When database is created works OK, but when not fails. Now the question is different: How can I create a field using the id from the same row?
--------ORIGINAL------
I'm working with active record in pure ruby (without Rails), and I'm literally getting crazy with this.
This is my code
class Enviroment < ActiveRecord::Base
#self.table_name = 'enviroments'
self.connection.create_table(:enviroments, :force=>true) do |t|
t.column :name, :string, :default=>'env-'+ (last.id-1).to_s
t.column :ssh, :string, :default=>nil
end
end
and here the error:
ActiveRecord::StatementInvalid: Could not find table 'enviroments'
from /usr/lib/ruby/gems/1.8/gems/activerecord-3.2.3/lib/active_record/connection_adapters/sqlite_adapter.rb:465:in `table_structure'
if I useself.table_name = 'enviroments' still not working. I've updated the gems and neither.
I'm newbie with ruby and databases, but I can't understand this problem, I think this same code worked in the past :S
Your code to create the table (very odd to have that in the model by the way) is calling last.id, and of course to call last the table must already exist.
Because you're passing :force => true to create_table you'll actually destroy the table if it already exists.
You could probably make your code work if you stashed the value of last.id in a local variable before the call to create_table but I don't understand why you are creating tables like this.
Finally, this was my solution:
class Enviroment < ActiveRecord::Base
after_create :create_default
private
def create_default
if name == nil
s = 'env-' + self.id.to_s
self.name = s
self.save
end
end
end
class CreateSchema < ActiveRecord::Migration
create_table(:enviroments, :force=>true) do |t|
t.column :name, :string, :default=>nil
t.column :ssh, :string, :default=>nil
end

(Object doesn't support #inspect)

I have a simple case, involving two model classes:
class Game < ActiveRecord::Base
has_many :snapshots
def initialize(params={})
# ...
end
end
class Snapshot < ActiveRecord::Base
belongs_to :game
def initialize(params={})
# ...
end
end
with these migrations:
class CreateGames < ActiveRecord::Migration
def change
create_table :games do |t|
t.string :name
t.string :difficulty
t.string :status
t.timestamps
end
end
end
class CreateSnapshots < ActiveRecord::Migration
def change
create_table :snapshots do |t|
t.integer :game_id
t.integer :branch_mark
t.string :previous_state
t.integer :new_row
t.integer :new_column
t.integer :new_value
t.timestamps
end
end
end
If I attempt to create a Snapshot instance in rails console, using
Snapshot.new
I get
(Object doesn't support #inspect)
Now for the good part. If I comment out the initialize method in snapshot.rb, then Snapshot.new works. Why is this happening?
BTW I am using Rails 3.1, and Ruby 1.9.2
This is happening because you override the initialize method of your base class (ActiveRecord::Base). Instance variables defined in your base class will not get initialized and #inspect will fail.
To fix this problem you need to call super in your sub class:
class Game < ActiveRecord::Base
has_many :snapshots
def initialize(params={})
super(params)
# ...
end
end
I had this symptom when I had a serialize in a model like this;
serialize :column1, :column2
Needs to be like;
serialize :column1
serialize :column2
I ran into this issue when I used an invalid association name in a joins.
For example,
Book.joins(:authors).first
Should be
Book.joins(:author).first
Assuming a Book model belongs_to an Author model.
This can also happen when you implement after_initialize, particularly if you are attempting to access attributes which were not included in your select. For instance:
after_initialize do |pet|
pet.speak_method ||= bark # default
end
To fix, add a test for whether the attribute exists:
after_initialize do |pet|
pet.speak_method ||= bark if pet.attributes.include? 'speak_method' # default`
end
I'm not sure exactly why, but I got this error when I accidentally misspelled 'belongs_to' as 'belong_to' in the associated class definition.
I believe you forgot to
rails db:migrate
Try calling .valid? on the new object to see if you can get a more helpful error.
In my case, I got this error from a block of code that creates a new instance of one of my models and assigns values to its fields. It turns out that my code was assigning a value to one of the fields that Rails couldn't match with that field's type. Calling valid? on the new object gave me a more helpful error (undefined method `to_f' for #<MatchData...).
I ran into this problem after trying to integrate devise authentication with an existing User model, I solved it by running command below:
$spring stop
Don't know the exact cause but hope it helps someone.
This is a misleading and nonspecific error. For instance, I just got it because I made a scope like this:
scope :posted, -> { where('posted_on_date <= ?', Date.today) }
when it should have been:
scope :posted, -> { where('post_on_date <= ?', Date.today) }
In my case, this was due to my mistakenly using the posted_on_date attribute.
I get this problem if the model contains an after_find.
The same error if you put the attribute type wrong:
attribute :publicar, :integer, default: true
instead of
attribute :publicar, :boolean, default: true
I was getting this error when running an ActiveRecord .where clause/method.
It was simply because there was a typo in the column name. Once I fixed the typo the query worked exactly as expected.
Wrong:
Package.where(scrape_nunber: 2)
Right (fixed typo in column name, and it works now):
Package.where(scrape_number: 2)
Just double check there isn't a typo in your column name(s) in the where clause.

Resources