This is the error I receive when I run a test for this file.
The error is in line 83 when I attempt to remove the user from the database using the 'destroy' method.
This is the repo in question frozen in time.
The error:
ActiveRecord::StatementInvalid:
PG::UndefinedColumn: ERROR: column courts.user_id does not exist
LINE 1: SELECT "courts".* FROM "courts" WHERE "courts"."user_id" = $...
I'm trying to connect the table users with the table court this way:
User is the administrator of many courts
A court can have only one administrator
As you can see, 'Court' has a foreign key linked to 'users', but called 'administrator'.
I want 'administrator' to be the alias of a user because in the future User and Court might have more relationships: 'owner' for example.
I can see that active record is building this query:
SELECT "courts".* FROM "courts" WHERE "courts"."user_id" = $...
Maybe this can be solved if Active Record build a query this way
SELECT "courts".* FROM "courts" WHERE "courts"."administrator_id" = $...
But I don't know how to do it and if that would be prudent.
Maybe there is another way to do this. Something tidier, I feel I'm not doing the association properly.
What do you recommend?
models/user.rb
class User < ActiveRecord::Base
before_save :format_input
# extend Devise::Models
# Include default devise modules .
devise :database_authenticatable,
# :validatable,
# :recoverable,
# :rememberable,
# :trackable,
# :confirmable,
# :omniauthable,
:registerable
# note that this include statement comes AFTER the devise block above
include DeviseTokenAuth::Concerns::User
validates :first_name, presence: true, length: { in: 1..20 }
validates :last_name, presence: true, length: { in: 1..20 }
validates :email, uniqueness: true
validates_format_of :email, with: /#/
validates :password, presence: true, length: { in: 8..20 }, :on => :create
has_many :courts, dependent: :destroy
private
def format_input
self.first_name = first_name.downcase.titleize
self.last_name = last_name.downcase.titleize
self.email = email.downcase
end
end
models/court.rb
class Court < ApplicationRecord
belongs_to :administrator, :class_name => :user
validates :name, presence: true, length: { in: 1..20 }
validates :address, presence: true, length: { in: 1..50 }
validates :description, presence: true, length: { in: 1..100 }
validates :description, presence: true, length: { in: 1..100 }
end
The issue is on this line.
has_many :courts, dependent: :destroy
By default, ActiveRecord assumes the foreign key is named <lowercased_parent_class_name>_id. In this case, when you delete a user, ActiveRecord is trying to delete the associated courts using the user_id foreign key which doesn't exist. Pass the foreign_key option to the has_many call.
has_many :courts, dependent: :destroy, foreign_key: :administrator_id
From the docs:
:foreign_key
Specify the foreign key used for the association. By default this is guessed to be the name of this class in lower-case and _id suffixed. So a Person class that makes a has_many association will use person_id as the default :foreign_key.
There's another issue. You should pass the uppercased class name as a string in the belongs_to call in the Court model.
belongs_to :administrator, class_name: 'User'
This is unrelated to the question but you might want to check if it is okay to delete a court when the user record is deleted.
Related
I have a mode named Exam.
There are some columns in exames:
:title
:subject_id
:exam_type
I want to know how to implement this:
class Exam < ApplicationRecord
validates :title, presence: true
validates :subject_id, presence: true, if: :no_exam_type?
def no_exam_type?
self.exam_type == ""
end
end
That is to say, I want to create a exam:
Exam.create(title: "first exam", exam_type: "something")
The subject_id must be exist, when exam_type is blank, such as exam_type="" or just do:
Exam.create(title: "first exam", subject_id: 3)
because exam_type has a default blank value.
But the subject_id doesn't necessary provide, when exam_type not blank, such as exam_type="something".
Exam.create(title: "first exam", exam_type: "something", subject_id: 3)
I test it, but no lucky.
How to do that? Thanks appreciate.
In Rails 5 belongs_to associations default to optional: false. Which means that the model will automatically validate the presence of the association.
class Thing < ApplicationRecord
belongs_to :other_thing
end
Thing.create!
# => ActiveRecord::RecordInvalid: Validation failed: other_thing can't be blank
So you need to set the association as optional and make sure the column is nullable.
class Exam < ApplicationRecord
belongs_to :subject, optional: true
validates :title, presence: true
validates :subject_id, presence: true, if: :no_exam_type?
def no_exam_type?
!self.exam_type.present?
end
end
Have you tried like this.
validates :subject_id, presence: true, :if => exam_type.blank?
you can refer the doc here to suite your requirement
use validates_presence_of instead.
validates_presence_of :subject_id, if: :no_exam_type?
def no_exam_type?
self.exam_type.nil?
end
I've tried to research this but oddly no one else seems to have this problem or I am not searching right. Anyway here is my user.rb. I can create a user no problem and my custom field, 'pin, gets set. I can update the 'pin' field. But if try to change name, email or password they fail because the pin is blank per the validation in my model. How to skip the pin validation if I am not updating it? Also, if I update my email, it still sends a confirmation but when I click on the link it validates the pin again and the confirmation fails. Also, I don't want to regenerate the hash as well in the after_validation helper.
class User < ApplicationRecord
has_many :profiles, dependent: :destroy
has_many :general_settings, dependent: :destroy
has_many :profile_settings, through: :profiles
has_many :tags, dependent: :destroy
has_many :videos, through: :profiles
belongs_to :account_type
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable
after_validation :create_parent_digest
validates :pin, presence: true, length:{ is: 4 }, confirmation: true
attr_accessor :pin, :pin_confirmation
def valid_pin?(pin)
Devise::Encryptor.compare(self.class, parent_digest, pin)
end
private
def create_parent_digest
self.parent_digest = Devise::Encryptor.digest(self.class, pin)
end
end
According to the Rails Guide, you can skip validations on save by adding `validate: false as on option. For example:
#user.save(validate: false)
You should use option on: http://guides.rubyonrails.org/active_record_validations.html#on
For example:
validates :pin, presence: true, length:{ is: 4 }, confirmation: true, on: :create
or you can create a custom context for validation
# in model
validates :pin, presence: true, length:{ is: 4 }, confirmation: true, on: :validate_pin
# in controller
#user.save(context: :validate_pin)
I recommend to do not use callbacks for creating something, just make create_parent_digest a public method and call it by hand in controller(or service object)
# UsersController
if #user.save
#user.create_parent_digest
...
end
Edit: you can do like this
# in model
validates :pin, presence: true, length:{ is: 4 }, confirmation: true, on: :validate_pin
# in controller
def create
#user = User.new params
if #user.valid?(:validate_pin)
#user.create_parent_digest
#user.save
else
# do something like render :new
end
end
I have a nested form called Transaction that includes a child object called Banking Information Form. The banking information validations should not be triggered when the parent's (transaction) transaction_mechanism field is set to "cheque". For some reason when I set the transaction_mechanism to "cheque" in the form, it's still calling the banking information form validations. In the bank_information_form model I have a method called cheque_transaction? that should prevent the validations from triggering if the transaction_mechanism is "cheque", but I'm receiving an error.
The error
no block given (yield)
transaction.rb
class Transaction < ActiveRecord::Base
# Require packages
require 'time'
# Associations
belongs_to :user
has_one :banking_information_form
# Nested attributes
accepts_nested_attributes_for :banking_information_form
# Validations
validates_associated :banking_information_form
validate :check_debit_or_credit
validates :transaction_mechanism, :transaction_kind, :debit, :salesforce_id, presence: true
validate :debit, :credit,numericality: { only_integer: true }
end
banking_information_form.rb
class BankingInformationForm < ActiveRecord::Base
# Associations
# Belongs to :transaction
belongs_to :owner, foreign_key: "transaction_id", class_name: "Transaction"
# Validations
validates :financial_institution, :transit_number, :bank_account_number, presence: true, :unless => :cheque_transaction?
validates :transit_number, length: {is: 5,
too_short: "Your transit number is 5 digits long.",
too_long: "Your transit number is 5 digits long."}
validates :bank_account_number, length: {minimum: 4, maximum: 12,
too_short: "Your bank account number will be between 4 and 12 digits long.",
too_long: "Your bank account number will be between 4 and 12 digits long."}
private
def cheque_transaction?
self.transaction.transaction_mechanism == "Cheque"
end
end
class Transaction < ActiveRecord::Base
has_one :banking_information_form
# Other code
end
As you described, your banking informations will be validated only when the parent(transcactions) mechanism is not 'Cheque' :
class BankingInformationForm < ActiveRecord::Base
belongs_to :transaction #this was missing in your code.
validates :financial_institution, :transit_number,
:bank_account_number, presence: true, unless: :cheque_transaction?
# other validations here
private
def cheque_transaction?
transaction.transaction_mechanism == "Cheque"
end
end
After some more trial and error- I found a way to get my conditional validation to work. I first changed the model name from 'Transaction' to 'AccountTransaction'. I did this to comply with the conventions of rails after I found out 'Transaction' is a reserved word in rails.
Once I changed the model name, I appended my association with inverse_of: :account_transaction. More can be read about inverse_of here.
account_transaction.rb
class AccountTransaction < ActiveRecord::Base
# Require packages
require 'time'
# Associations
belongs_to :user
has_one :banking_information_form, inverse_of: :account_transaction
# Nested attributes
accepts_nested_attributes_for :banking_information_form
# Validations
validates_associated :banking_information_form
validate :check_debit_or_credit
validates :transaction_mechanism, :transaction_kind, :debit, :salesforce_id, presence: true
validate :debit, :credit,numericality: { only_integer: true }
end
banking_information_form.rb
class BankingInformationForm < ActiveRecord::Base
# Associations
belongs_to :account_transaction
# Validations
validates :financial_institution, :transit_number, :bank_account_number, presence: true, :unless => :cheque_transaction?
validates :transit_number, length: {is: 5,
too_short: "Your transit number is 5 digits long.",
too_long: "Your transit number is 5 digits long."}, :unless => :cheque_transaction?
validates :bank_account_number, length: {minimum: 4, maximum: 12,
too_short: "Your bank account number will be between 4 and 12 digits long.",
too_long: "Your bank account number will be between 4 and 12 digits long."}, :unless => :cheque_transaction?
private
def cheque_transaction?
self.account_transaction.transaction_mechanism == "Cheque"
end
end
I have model Order:
class Order < ActiveRecord::Base
has_one :shipping_address
has_and_belongs_to_many :books
validates :first_name, :surename, :email, :street1, :country, :zipcode, presence: true
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
validates :zipcode, numericality: true
accepts_nested_attributes_for :shipping_address
end
and model Book:
class Book < ActiveRecord::Base
DEFAULT_PRICE = 55.15
NEXT_BOOK_PERCENT = 5
has_and_belongs_to_many :pages
has_and_belongs_to_many :orders
validates :name, presence: {message: "Name can't be blank."}
validates_length_of :name, minimum: 3, maximum: 12, message: "Sorry, we can't create this book right now. Please contact us for further information."
validate :same_letter_validation
validate :validates_for_non_alphabetic
before_save :compile
#......
end
Also I have table books_orders (book_id, order_id)
When I try do delete order from RailsAdmin panel I get next error:
NoMethodError in RailsAdmin::Main#delete
undefined method `orders_books' for #
It says that error in this line:
- #abstract_model.each_associated_children(object) do |association, child|
Have you defined that "orders_books" method anywhere in your code? If so, can you please add it to your question. If you haven't, then the root cause of your issue is just that, that you're calling the "orders_books" method but it is not yet defined
Given that you reference "#books_orders" in your question, I believe it likely that you just swapped "books_orders" and "orders_books" at some point in your code
Thanks. It's bug of a Rails 4.1.1. I have update it to 4.1.4 and all works OK.
Edited this old question to include an answer from below: Rubocop has it https://github.com/rubocop-hq/rails-style-guide#macro-style-methods
Rails is all about 'Convention over Configuration'. However, I haven't come across a 'standard' for the order of associations, scopes, includes, validations, ... in Rails models yet. Take for example the following, simplified, product model:
class Product < ActiveRecord::Base
mount_uploader :logo, AssetUploader
acts_as_taggable
paginates_per 50
include ActionView::Helpers::NumberHelper
belongs_to :company
validates_presence_of [:title, :price, :plu]
scope :on_website, where(display: true)
def display_price
...
end
end
Is this the correct order? It's maybe not that important to many people, but I personally think it would be great if there were a convention on this.
There is no such convention. But you can create one for your project and be consistent with it in all the models. This is what i follow.
class Model < ActiveRecord::Base
#all mixins
include Something
extend Something
#other stuff
acts_as_taggable
paginates
#associations
has_many :something
belongs_to :something_else
#validations
validate_presence_of :something
#scopes
scope :something
#instance methods
def instance_method
end
#class methods
def self.method
end
#private methods
private
def method2
end
end
Rubocop has it
https://github.com/rubocop-hq/rails-style-guide#macro-style-methods
class User < ActiveRecord::Base
# keep the default scope first (if any)
default_scope { where(active: true) }
# constants come up next
COLORS = %w(red green blue)
# afterwards we put attr related macros
attr_accessor :formatted_date_of_birth
attr_accessible :login, :first_name, :last_name, :email, :password
# Rails 4+ enums after attr macros
enum gender: { female: 0, male: 1 }
# followed by association macros
belongs_to :country
has_many :authentications, dependent: :destroy
# and validation macros
validates :email, presence: true
validates :username, presence: true
validates :username, uniqueness: { case_sensitive: false }
validates :username, format: { with: /\A[A-Za-z][A-Za-z0-9._-]{2,19}\z/ }
validates :password, format: { with: /\A\S{8,128}\z/, allow_nil: true }
# next we have callbacks
before_save :cook
before_save :update_username_lower
# other macros (like devise's) should be placed after the callbacks
...
end