I have the following (simplified) model and migration:
Model:
class User < ActiveRecord::Base
attr_readonly :contacted
validates :contacted, :inclusion => { :in => [true, false] }
def set_contacted
self.contacted = true
end
def unset_contacted
# self.contacted = false
self.contacted = "0"
end
end
Migration:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.boolean :contacted, :null => false, :default => false
t.timestamps
end
end
end
As you can kind of see in the comment in my model, setting the variable contact to false results in an error - I can only set it to "0". Why? I don't see how "false" would violate the null constraint, right?
Edit:
For clarification, I am using PostgreSQL and ActiveRecord. The error that I'm getting is this:
C:/Ruby193/lib/ruby/gems/activerecord-3.2.8/lib/active_record/validations.rb:56:in 'save!' Validation failed: ActiveRecord::RecordInvalid)
I get that error even if I remove the "validates" statement from my model, and even if I remove the NULL constraint from the migration. It's something to do with setting the value of the attribute to be false. Is there some odd constraint on ActiveRecord booleans?
It's a bit difficult answering your question without having the specific error information.
First I'd change attr_readonly to attr_accessible - So the field will be updatable.
Secondly, I'd re-write your method:
def unset_contacted
self.contacted = false
self.save! # Saving your methods (the ! is for throwing an exception if it fails).
end
No one seems able to solve this, but it' no longer an issue for me. My model is better served by using a state_machine gem, so I removed this field altogether.
Related
I have the following code in my Rails 'seeds.rb' file:
acondigitaladdresslineone = Addressline.create!(address_id: acondigitaladdress.id, address: "Sørkedalsveien 273", address_line_position: 0)
The 'Address' model has a one-to-many relation with the 'Addressline' model, and the respective 'belongs_to' and 'has_many' declarations are properly in place. Here is the content of the migration files:
class CreateAddresses < ActiveRecord::Migration[5.0]
def change
create_table :addresses do |t|
t.references :towncity, foreign_key: true
t.references :stateregion, foreign_key: true, null: true
t.references :postalcode, foreign_key: true
t.timestamps
end
end
end
class CreateAddresslines < ActiveRecord::Migration[5.0]
def change
create_table :addresslines do |t|
t.references :address, foreign_key: true
t.string :address
t.integer :address_line_position
t.timestamps
end
end
end
When I run the Rails Console, I receive the following error, which refers to the "acondigitaladdresslineone = ..." line of code:
ActiveRecord::AssociationTypeMismatch: Address(#8269560) expected, got String(#6922340)
I corrected the error by making the following changes:
# From the 'CreateAddresslines' migration file
# Original code
t.string :address
# Revised code
t.string :address_name
# From the 'acondigitaladdresslineone' variable declaration line
# Original code
address: "Sørkedalsveien 273"
# Revised code
address_name: "Sørkedalsveien 273"
I suspect the cause of the error possibly is due the underlying database's (in this case SQLite) inability to disambiguate the same field name based upon datatype. For example, in the original version of the 'CreateAddresslines' migration file, SQLlite raises an error as a result of these two declarations:
t.references :address, foreign_key: true
t.string :address
It is easy to overlook the obvious when entangled in coding. Clearly, a relational database table cannot have two or more columns with the same name. I am unaware of any relational database management system having a capability to disambiguate same-named table columns based upon differences in their datatypes.
I need a sanity check. Is this a reasonable assumption why the 'AssociationTypeMismatch' error was raised?
Thank you.
t.references :address will create the column address_id so there should be no conflict with t.string :address on the DB level.
The problem occurs in ActiveRecord when accessors and associations are declared.
ActiveRecord reads the schema from the database and uses it to create attributes and accessor methods in your models for each column. This happens before any of the code in the class declaration block is evaluated.
When you then create an association you are overwriting the #address and #address= methods created for the string attribute.
class Addressline
belongs_to :address
end
Which is why when you do:
Addressline.new(address: 'Foo')
You get a type error since the #address= setter method is setting address_id and expects an Address instance.
The solution is as you might have surmised is to not name other columns the same thing as your associations. Just rename the column:
class FixColumnName < ActiveRecord::Migration
def self.change
# call it whatever you want, but not address!
rename_column :addresslines, :address, :value
end
end
I am running into an error after generating a new scaffold for a table with an existing model: NOT NULL constraint failed: questions.question_text.
This has been covered before, but I have not seen an answer for when there is a set null value, as I have done below.
First, I had already generated a model/migration for this table entitled Questions, which looks like:
..._create_questions.rb
class CreateQuestions < ActiveRecord::Migration
def change
create_table :questions do |t|
t.belongs_to :category, index: true
t.string :question_text, :null => false
t.timestamps
end
end
end
Notice here that I am specifying the null => false. To save some time, I ran a Scaffold command to allow me to enter data into Questions easily:
rails generate scaffold Questions --skip
After restarting the server, I am running into the error above. Since I am directly addressing the null value, I am unclear of why it triggers an error when I reach the block in QuestionsController#create (in other words, when I try to create a Question).
In case it helps, here is my Question.rb model as well:
class Question < ActiveRecord::Base
has_many :quiz_questions
has_many :quizzes, through: :quiz_questions
has_many :answers
belongs_to :category
accepts_nested_attributes_for :answers
end
What am I doing wrong?
If you are using not-null as a form of validation than you would want to add a model validation which enforces this rule as well:
class Question < ActiveRecord::Base
# ...
validates_presence_of :question_text
end
This will prevent database driver level exceptions and provide user feedback.
Since you ran the scaffold generator without any attributes I'm guessing that the params whitelist might be empty as well which would cause the above validation to fail since the input is never actually passed to the initializer.
class QuestionsController < ApplicationController
def create
#question = Question.create(question_params)
# ...
end
private
def question_params
params.require(:question)
.permit(
:category_id,
:question_text
answers_attributes: [:foo, :bar, :baz]
)
end
end
I am trying to make a conditional validation of a field. Such that it only validates if another field is a specific value. The problem here is, that this other field is a one to many relation, and I can't seem to get it working.
Here is the relevant code:
class CreateInvolvedPartyTypes < ActiveRecord::Migration
def change
create_table :involved_party_types do |t|
t.string :code
t.string :name
t.timestamps null: false
end
end
end
class CreateInvolvedParties < ActiveRecord::Migration
def change
create_table :involved_parties do |t|
t.string :first_name
t.string :last_name
t.references :involved_party_type
t.timestamps null: false
end
end
end
class InvolvedParty < ActiveRecord::Base
def ipt_cd?
self.involved_party_type.code == 'I'
end
validates :first_name, presence: { message: "Please insert first name" }
validates :last_name, presence: { message: "Please insert last name" }, :if => :ipt_cd?
validates :involved_party_type, presence: { message: "Please select involved party type" }
belongs_to :involved_party_type
end
The above code fails with:
undefined method `code' for nil:NilClass
Thanks for your help
The error means that self.involved_party_type in InvolvedParty#ipt_cd? is nil. You should test the presence of involved_party_type before calling #code on it, or use #try.
def ipt_cd?
return false if involved_party.nil?
involved_party_type.code == 'I'
end
def ipt_cd?
self.involved_party_type.try(:code) == 'I'
end
Or you can avoid the problem by only invoking the validation if involved_party_type exists.
validates :last_name, presence: { message: "Please insert last name" }, if: -> { involved_party_type && ipt_cd? }
I think the issue is that you're getting confused with calling instance and class level data.
"Instance" data is populated each time a class is invoked
"Class" data is static, always appending to the class
The cosmetic difference between the two is that class data is typically called through self (EG def self.method & self.attribute), whilst instance data is called with "naked" attributes (IE without self).
You're calling the following:
def ipt_cd?
self.involved_party_type.code == 'I'
end
The problem is that you're referencing self as if it's a piece of class data. What you want is the instance equivalent:
def ipt_cd?
involved_party_type.code == 'I'
end
As the other answer states, your error is caused by a piece of data having no method for code, meaning it's nil.
The casue of this is here (solution is above -- IE remove self):
involved_party_type.code == 'I'
Thus, if you want to make sure you don't receive this error, you'll have to ensure that involved_party_type is present. This can be done by first ensuring you're referencing the instance variant of the data, followed by ensuring it's there anyway. The other answer provided the best way to achieve that.
Finally, I think your structure could be improved.
Referencing the actual data representation of an associated field is bad practice in my opinion. You're trying to create a new piece of data, and yet you're referencing an associated attribute?
Why not do the following:
#app/models/party_type.rb
class PartyType < ActiveRecord::Base
has_many :involved_parties
end
class InvolvedParty < ActiveRecord::Base
belongs_to :party_type
validates :first_name, :party_type, presence: true
validates :last_name, presence: { message: "Please insert last name" }, if: :cd?
private
def cd?
party_type == PartyType.find_by(code: "I").pluck(:id)
end
end
This will send another DB query but it removes the dependency on specific data. Your current setup is not relying on foreign keys, but on a value which may change.
Whilst this recommendation also relies on data (IE code == I), it uses it as a quantifier within ActiveRecord. That is, you're not comparing the data, but the relationship.
I've been trying to have my rails project only update the user table with the users unique facebook data. However, I can't get the facebook data to populate. I've tried multiple approaches but the end code seems to be hacky and using brute force to update the columns (as well as creating duplicate records)
Here are my examples:
User
class User < ActiveRecord::Base
has_one :facebook
def self.create_with_omniauth(auth)
create! do |user|
user.email = auth['email']
end
end
end
Facebook
class Facebook < ActiveRecord::Base
belongs_to :user
def self.create_with_omniauth(auth)
create! do |fb|
if auth['info']
fb.profile_link = auth['info']['profile_link'] || "test"
end
end
end
Migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.timestamps null: false
end
end
end
class Facebooks < ActiveRecord::Migration
create_table :facebooks do |f|
f.belongs_to :user, index: true, :unique => true
f.string :profile_link
f.timestamps null: false
end
end
While creating the user:
SessionController (When calling create for user)
def create
auth = request.env["omniauth.auth"]
user = User.where(:provider => auth['provider'],
:uid => auth['uid'].to_s).first || User.create_with_omniauth(auth)
Facebook.create_with_omniauth(auth)
My understanding of Rails ActiveRecord so far... is that if I use "has_one" and "belongs_to" then it should automatically create records in the facebook table if a user table was created?
My expected Data would be:
SELECT * FROM users where id = 1;
id email
1 email#email.com
SELECT * FROM facebooks where user_id = 1;
id user_id profile_link
1 1 facebook.com/profile_link
facebook has no record created at all.
Not sure where I went wrong, I've followed tons of tutorials and hope I can master the active record.
Thanks!
Side Question for #val
def self.facebook_handler(user, auth)
if Facebook.exists?(user_id: id)
user = Facebook.find_by(user_id: id)
user.update(name: me['name'])
user.update(first_name: me['first_name'])
else
create! do |fb|
if me
fb.name = me['name']
fb.user_id = user.id
fb.first_name = me['first_name']
end
end
end
end
--- otherwise it kept inserting new records each time I logged in.
So many moving pieces in activerecord and in Rails. I think you have to go back to your migration and address a few things to set a solid model foundation for the view and controller parts of your MVC.
I see model-type function in the migration you posted, which is not going to serve you well. Migrations should be as flexible as possible, the constraints should be placed on the model.rb.
Migration: Flexible. Basic relationship indices set up.
Model: The
model.rb defines constraints (has_one, belongs_to, etc) and further
embellishes and validates data relationships (:dependent,:required,
etc.)
Your users model looks fine.
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.timestamps null: false
end
end
end
Your facebooks migration should have looked more like this. Create a t.reference and add the index.
class Facebooks < ActiveRecord::Migration
create_table :facebooks do |f|
t.references :user, index: true
f.string :profile_link
f.timestamps null: false
end
add_index :facebooks, [:user_id]
end
Then in your Facebook model you can apply restraints and requirements
facebook.rb
belongs_to :user,
validates :user_id, presence: true, :unique => true
Your user model.rb should include:
has_one :facebook
There are some other questions about your higher level actions in the controller, but I think setting up your model will help you make progress towards your goal.
The model constraints below, along with the index setup looks like it would cause ActiveRecord to ROLLBACK and not add a duplicate facebook record for a given user. But it sounds like duplicates are being added to the facebook table. So, how?
facebook.rb
belongs_to :user,
validates :user_id, presence: true, :unique => true
...
user.rb
has_one :facebook
The 'if' clause you wrote looks to me as if it would be unnecessary if the relationship between user / facebook are set up and working in the model and database table, which makes me think there's a missing validation somewhere.
There's something to try, a model migration (change) on Facebook data description to add a :unique validator to the user_id field of the db table itself. (There's no change_index command, you have to remove and then add.)
remove_index :facebooks, [:user_d]
add_index :facebooks, [:user_id], :unique => true
Try taking your 'if' logic out and see if you're getting dupes. The relationships need to be properly setup before proceeding to the logic in the controller or you will break your head trying to unwind it.
And to your question in the comment, scopes are beautiful for creating collections based on parameters. So, in your user.rb model:
scope :important_thing_is_true, -> { where(:provider => auth['provider'],:uid => auth['uid'].to_s).first) }
Which is referenced by user.important_thing_is_true returns the collection or nil, which then you can test or use in other logic or display, etc. But, if you don't have the dupe records problem, maybe this scope isn't needed.
I'm in the following situation, taking over an existing website, I have model User which has many devices like that:
has_many :devices, :through => :credits
When I create a device it creates a credit, but some of the attributes of the credits are null. I'd like to know if there's a way to control the creation of this credit and make sure nothing is null in the credit created for the database.
Thanks in advance
Recommended:
Use default values in your credits database table. You can use a database migration to do this.
class AddDefaultValuesToCredits < ActiveRecord::Migration
def change
change_column :credits, :value1, :boolean, default: false
change_column :credits, :value2, :string, default: 'words'
# change other columns
end
end
If no explicit value is specified for value1 or value2, they'll default to false and 'words', respectively.
Alternative: You can also set default values in your Credit model.
class Credit < ActiveRecord::Base
after_initialize :set_values, unless: persisted?
# other model code
def set_values
if self.new_record?
self.value1 = false if self.value.nil? # initial boolean value
self.value2 ||= 'words' # initial string value
# other values
end
end