I'm trying to run a migration to delete two rows in the database. Here's the migration file ...
class RemoveMenuItem < ActiveRecord::Migration
def up
MenuItem.delete(name: "Validation Settings")
MenuItem.delete(name: "Identifier Lookup")
end
def down
MenuItem.create(name: "Validation Settings", type: "MenuItem", actionable_item_id: 89, actionable_items_count: 0, sequence: 20)
MenuItem.create(name: "Identifier Lookup", type: "MenuItem", actionable_item_id: 89, actionable_items_count: 0, sequence: 30)
end
end
... but I'm getting this error ...
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "id"
LINE 1: ...ERE "actionable_items"."type" IN ('MenuItem') AND "id"."name...
^
: DELETE FROM "actionable_items" WHERE "actionable_items"."type" IN ('MenuItem') AND "id"."name" = 'Validation Settings'/Users/kweihe/.rvm/gems/ruby-2.1.6/gems/activerecord-3.2.22/lib/active_record/connection_adapters/postgresql_adapter.rb:1163:in `async_exec'
Deletes require an identified record to work. Try this:
def up
MenuItem.find_by(name: "Validation Settings").delete
....
I agree with the commenter though - this doesn't seem like something you necessarily want in your migrations. Probably better in a seed file.
SOLUTION
class RemoveActionableItems < ActiveRecord::Migration
class ActionableItem < ActiveRecord::Base
attr_accessible :actionable_item, :name, :sequence, :type
end
def up
validation_settings = MenuItem.find_by_name("Validation Settings")
identifier_lookup = MenuItem.find_by_name("Identifier Lookup")
compliance = InsightReportMenuItem.find_by_name("Compliance")
validation_settings.delete unless validation_settings.nil?
identifier_lookup.delete unless identifier_lookup.nil?
compliance.delete unless compliance.nil?
end
def down
MenuItem.create :name => "Validation Settings", :type => "MenuItem"
MenuItem.create :name => "Identifier Lookup", :type => "MenuItem"
InsightReportMenuItem.create :name => "Compliance", :type => "InsightReportMenuItem"
end
end
Related
Related/Fixed: Ruby on Rails: Validations on Form Object are not working
I have the below validation..
validates :age, numericality: { greater_than_or_equal_to: 0,
only_integer: true,
:allow_blank => true
}
It is not required, if entered needs to be a number. I have noticed that if someone types in a word instead of a number, the field value changes to 0 after submit and passes validation. I would prefer it to be blank or the entered value.
Update:
Still no solution, but here is more information.
rspec test
it "returns error when age is not a number" do
params[:age] = "string"
profile = Registration::Profile.new(user, params)
expect(profile.valid?).to eql false
expect(profile.errors[:age]).to include("is not a number")
end
Failing Rspec Test:
Registration::Profile Validations when not a number returns error when age is not a number
Failure/Error: expect(profile.errors[:age]).to include("is not a number")
expected [] to include "is not a number"
2.6.5 :011 > p=Registration::Profile.new(User.first,{age:"string"})
2.6.5 :013 > p.profile.attributes_before_type_cast["age"]
=> "string"
2.6.5 :014 > p.age
=> 0
2.6.5 :015 > p.errors[:age]
=> []
2.6.5 :016 > p.valid?
=> true
#Form Object Registration:Profile:
module Registration
class Profile
include ActiveModel::Model
validates :age, numericality: { greater_than_or_equal_to: 0,
only_integer: true,
:allow_blank => true
}
attr_reader :user
delegate :age , :age=, to: :profile
def validate!
raise ArgumentError, "user cant be nil" if #user.blank?
end
def persisted?
false
end
def user
#user ||= User.new
end
def teacher
#teacher ||= user.build_teacher
end
def profile
#profile ||= teacher.build_profile
end
def submit(params)
profile.attributes = params.slice(:age)
if valid?
profile.save!
true
else
false
end
end
def self.model_name
ActiveModel::Name.new(self, nil, "User")
end
def initialize(user=nil, attributes={})
validate!
#user = user
end
end
end
#Profile Model:
class Profile < ApplicationRecord
belongs_to :profileable, polymorphic: true
strip_commas_fields = %i[age]
strip_commas_fields.each do |field|
define_method("#{field}=".intern) do |value|
value = value.gsub(/[\,]/, "") if value.is_a?(String) # remove ,
self[field.intern] = value
end
end
end
The interesting thing is that if move the validation to the profile model and check p.profile.errors, I see the expected result, but not on my form object. I need to keep my validations on my form object.
If the underlying column in the DB is a numeric type, then Rails castes the value. I assume this is done in [ActiveRecord::Type::Integer#cast_value][1]
def cast_value(value)
value.to_i rescue nil
end
Assuming model is a ActiveRecord model where age is a integer column:
irb(main):008:0> model.age = "something"
=> "something"
irb(main):009:0> model.age
=> 0
irb(main):010:0>
This is because submitting a form will always submit key value pairs, where the keys values are strings.
No matter if your DB column is a number, boolean, date, ...
It has nothing to do with the validation itself.
You can access the value before the type cast like so:
irb(main):012:0> model.attributes_before_type_cast["age"]
=> "something"
If your requirements dictate another behaviour you could do something like this:
def age_as_string=(value)
#age_as_string = value
self.age = value
end
def age_as_string
#age_as_string
end
And then use age_as_string in your form (or whatever). You can also add validations for this attribute, e.g.:
validates :age_as_string, format: {with: /\d+/, message: "Only numbers"}
You could also add a custom type:
class StrictIntegerType < ActiveRecord::Type::Integer
def cast(value)
return super(value) if value.kind_of?(Numeric)
return super(value) if value && value.match?(/\d+/)
end
end
And use it in your ActiveRecord class through the "Attributes API":
attribute :age, :strict_integer
This will keep the age attribute nil if the value you are trying to assign is invalid.
ActiveRecord::Type.register(:strict_integer, StrictIntegerType)
[1]: https://github.com/rails/rails/blob/fbe2433be6e052a1acac63c7faf287c52ed3c5ba/activemodel/lib/active_model/type/integer.rb#L34
Why don't you add validations in frontend? You can use <input type="number" /> instead of <input type="text" />, which will only accept number from the user. The way I see you explaining the issue, this is a problem to be resolved in the frontend rather than backend.
You can read more about it here: Number Type Input
Please let me know if this doesn't work for you, I will be glad to help you.
Would this be considered bad practice?
unless Link.exists?(:href => 'example.com/somepage')
Domain.where(:domain => 'example.com').first.links.create(:href => 'example.com/somepage', :text => 'Some Page')
end
I realize I might be requesting more data then I actually need, can I optimize this somehow?
Domain is a unique index so the lookup should be fairly quick.
Running Rails 3.0.7
You can refactor your code in this manner:
Domain class
class Domain < ActiveRecord::Base
has_many :links
end
Link class
class Link < ActiveRecord::Base
belongs_to :domain
validates :href,
:uniqueness => true
attr :domain_url
def domain_url=(main_domain_url)
self.domain = Domain.where(domain: main_domain_url).first ||
Domain.new(domain: main_domain_url)
end
def domain_url
self.domain.nil? ? '' : self.domain.domain_url
end
end
Usage
Link.create(href: 'example.com/somepage',
text: 'Some Page',
domain_url: 'example.com')
Conclusion
In both cases (your and mine) you get two request (like so):
Domain Load (1.0ms) SELECT "domains".* FROM "domains" WHERE "domains"."domain" = 'example.com' LIMIT 1
AREL (0.1ms) INSERT INTO "links" ("href", "text", "domain_id", "created_at", "updated_at") VALUES ('example.com/somepage', 'Some Page', 5, '2011-04-26 08:51:20.373523', '2011-04-26 08:51:20.373523')
But with this code you're also protected from unknown domains, so Link'd create one automatically.
Also you can use validates uniqueness so you can remove all unless Link.exists?(:href => '...').
Domain.where(:domain => 'example.com').
first.links.
find_or_create_by_href_and_text(:href => 'example.com/somepage', :text => "Some Page")
UPD
#domain = Domain.where(:domain => 'example.com').
first.links.
find_or_create_by_href('example.com/somepage')
#domain.text = "My Text"
#domain.save
Or you can use extended update_or_create_by_* method:
Domain.update_or_create_by_href('example.com/somepage') do |domain|
domain.text = "My Text"
end
More info here:
find_or_create_by in Rails 3 and updating for creating records
Suppose I have a model class like this:
class Shoebox < ActiveRecord::Base
validates_inclusion_of :description, :in => ["small", "medium"],
:message => I18n.t("activerecord.errors.models.shoebox.with_name",
:name => name)
end
And some yaml:
en:
activerecord:
errors:
models:
shoebox:
with_name: "the description of %{name} is not in the approved list"
And I create a new Shoebox:
s = Shoebox.new(:description => "large", :name => "Bob")
s.valid?
But when I look at the error (s.errors.first.message), I see:
"the description of Shoebox is not in
the approved list"
and not:
"the description of Bob is not in the
approved list"
I've tried :name => name, :name => :name, :name => lambda{name}, :name => lambda{:name}.
I've tried creating a helper method
def shoebox_name
name
end
And passing :name => shoebox_name, :name => :shoebox_name, :name => lambda{shoebox_name} and :name => lambda {:shoebox_name}.
How can I get the ivar value for name to be interpolated into the string?
Try removing the message option in the validation, and change your yaml to be:
en:
activerecord:
errors:
models:
shoebox:
description:
inclusion: "the description of %{name} is not in the approved list"
See http://guides.rubyonrails.org/i18n.html#translations-for-active-record-models for more details
You can use a custom validation method to achieve what you are trying to do. All the columns are available in the custom validator:
validate :description_in
def description_in
if !(["small", "medium"].include?(description))
errors.add(:base, "The description of #{name} is not in the approved list")
end
end
PS: After a lot of googling around, I realized that it was much easier to implement a custom validator than search around.
I have used globalize2 to add i18n to an old site. There is already a lot of content in spanish, however it isn't stored in globalize2 tables. Is there a way to convert this content to globalize2 with a migration in rails?
The problem is I can't access the stored content:
>> Panel.first
=> #<Panel id: 1, name: "RT", description: "asd", proje....
>> Panel.first.name
=> nil
>> I18n.locale = nil
=> nil
>> Panel.first.name
=> nil
Any ideas?
I'm sure you solved this one way or another but here goes. You should be able to use the read_attribute method to dig out what you're looking for.
I just used the following to migrate content from the main table into a globalize2 translations table.
Add the appropriate translates line to your model.
Place the following in config/initializers/globalize2_data_migration.rb:
require 'globalize'
module Globalize
module ActiveRecord
module Migration
def move_data_to_translation_table
klass = self.class_name.constantize
return unless klass.count > 0
translated_attribute_columns = klass.first.translated_attributes.keys
klass.all.each do |p|
attribs = {}
translated_attribute_columns.each { |c| attribs[c] = p.read_attribute(c) }
p.update_attributes(attribs)
end
end
def move_data_to_model_table
# Find all of the translated attributes for all records in the model.
klass = self.class_name.constantize
return unless klass.count > 0
all_translated_attributes = klass.all.collect{|m| m.attributes}
all_translated_attributes.each do |translated_record|
# Create a hash containing the translated column names and their values.
translated_attribute_names.inject(fields_to_update={}) do |f, name|
f.update({name.to_sym => translated_record[name.to_s]})
end
# Now, update the actual model's record with the hash.
klass.update_all(fields_to_update, {:id => translated_record['id']})
end
end
end
end
end
Created a migration with the following:
class TranslateAndMigratePages < ActiveRecord::Migration
def self.up
Page.create_translation_table!({
:title => :string,
:custom_title => :string,
:meta_keywords => :string,
:meta_description => :text,
:browser_title => :string
})
say_with_time('Migrating Page data to translation tables') do
Page.move_data_to_translation_table
end
end
def self.down
say_with_time('Moving Page translated values into main table') do
Page.move_data_to_model_table
end
Page.drop_translation_table!
end
end
Borrows heavily from Globalize 3 and refinerycms.
I have problems to get rspec running properly to test validates_inclusion_of my migration looks like this:
class CreateCategories < ActiveRecord::Migration
def self.up
create_table :categories do |t|
t.string :name
t.integer :parent_id
t.timestamps
end
end
def self.down
drop_table :categories
end
end
my model looks like this:
class Category < ActiveRecord::Base
acts_as_tree
validates_presence_of :name
validates_uniqueness_of :name
validates_inclusion_of :parent_id, :in => Category.all.map(&:id), :unless => Proc.new { |c| c.parent_id.blank? }
end
my factories:
Factory.define :category do |c|
c.name "Category One"
end
Factory.define :category_2, :class => Category do |c|
c.name "Category Two"
end
my model spec looks like this:
require 'spec_helper'
describe Category do
before(:each) do
#valid_attributes = {
:name => "Category"
}
end
it "should create a new instance given valid attributes" do
Category.create!(#valid_attributes)
end
it "should have a name and it shouldn't be empty" do
c = Category.new :name => nil
c.should be_invalid
c.name = ""
c.should be_invalid
end
it "should not create a duplicate names" do
Category.create!(#valid_attributes)
Category.new(#valid_attributes).should be_invalid
end
it "should not save with invalid parent" do
parent = Factory(:category)
child = Category.new #valid_attributes
child.parent_id = parent.id + 100
child.should be_invalid
end
it "should save with valid parent" do
child = Factory.build(:category_2)
child.parent = Factory(:category)
# FIXME: make it pass, it works on cosole, but I don't know why the test is failing
child.should be_valid
end
end
I get the following error:
'Category should save with valid
parent' FAILED Expected #<Category id:
nil, name: "Category Two", parent_id:
5, created_at: nil, updated_at: nil>
to be valid, but it was not Errors:
Parent is missing
On console everything seems to be fine and work as expected:
c1 = Category.new :name => "Parent Category"
c1.valid? #=> true
c1.save #=> true
c1.id #=> 1
c2 = Category.new :name => "Child Category"
c2.valid? #=> true
c2.parent_id = 100
c2.valid? #=> false
c2.parent_id = 1
c2.valid? #=> true
I'm running rails 2.3.5, rspec 1.3.0 and rspec-rails 1.3.2
Anybody, any idea?
The problem is that you can't put a call to Category.all.map(&:id) inside the called to validates_inclusion_of.
The first indication that this is the case will be apparent when you try to run
rake db:migrate:down VERSION=<n>
rake db:migrate:up VERSOIN=<n>
where <n> is the version number of the migration that creates the Category model.
You will get something like:
in /Users/sseefried/tmp/so)
== CreateCategories: reverting ===============================================
-- drop_table(:categories)
-> 0.0032s
== CreateCategories: reverted (0.0034s) ======================================
(in /Users/sseefried/tmp/so)
rake aborted!
SQLite3::SQLException: no such table: categories: SELECT * FROM "categories"
(See full trace by running task with --trace)
This is because rake tries to load app/models/category.rb before running the migration. Because the Category model does not exist it fails.
Another way to see the problem is to do tail -f log/development.log and then try to open a console with script/console. You will see an SQL query of the form:
SELECT * FROM "categories"
in the output. This corresponds to the call to Category.all.map(:&id). However, once you start typing commands like:
c1 = Category.new, :name => "Category 1"
you will see that the query SELECT * from "categories" does not reappear in the log. The moral of the story is only constants can appear in calls to validations_inclusion_of because the code in there will only be evaluated once..
The only reason your console code worked was because, in a previous console session, you had created a Category object with id=1
You can write a custom validation that does what you want with:
validate :parent_exists
protected
def parent_exists
ids = Category.all.map(&:id)
if !parent_id.blank? && !ids.member?(parent_id)
errors.add(:parent_id, "does not point to a valid parent record")
end
end
Your rspec tests will pass once you have added this code.
Actually, you can defer enumerable calculation by simply putting Category.all.map(&:id) into a proc/lambda.
Writing
validates_inclusion_of :parent_id,
in: proc{ Category.all.map(&:id) },
unless: proc{ |c| c.parent_id.blank? }
will fetch categories' ids at the time of validation, not at the time of class declaration.