I have two classes, a User and a UserGroup class.
A User has one UserGroup and a UserGroup can have many Users
When I create a new UserGroup, say "Group 1", it appears in my list of UserGroups and I can edit it and save it without problems.
However, when I go to create a new User, I can see and select my new UserGroup, "Group 1" from the dropdown list, but when I go to save I get a validation error because Rails doesn't see the UserGroup id as belonging to the current list of UserGroup ids.
Here are pieces of what I believe are the relevant code:
user_group model:
class UserGroup < ActiveRecord::Base
...
...
# class methods
def self.full_list_of_ids
UserGroup.all.pluck(:id)
end
end
user model:
class User < ActiveRecord::Base
...
...
validates :user_group_id, inclusion: { in: UserGroup.full_list_of_ids }, unless: 'Rails.env.test?'
...
...
end
The error that occurs when I try to save the new User with the new UserGroup is a validation error so it seems the code within full_list_of_ids returns back an older version of the UserGroup ids even although within the views, I can see the new UserGroup.
I am running this within my development environment at the moment.
So is there a way to force Rails to reload the version of UserGroups in memory or something else?
It seems like it's just caching things too much to me. Surely a newly created object within a class should cause a reload automatically?
I should point out that I can change the UserGroup of any User to an older UserGroup no problems.
If you need any more information, let me know. Thanks.
In 11 years I haven't seen someone using an inclusion validation like this. What you want to do is according to the docs:
class User < ActiveRecord::Base
belongs_to :user_group
validate :user_group, presence: true
end
Now to explain why you ran into this problem: the argument of validate are evaluated when the class is loaded, so the UserGroup.full_list_of_ids is evaluated into an array and that array is not updated after creating a new usergroup. Don't ask for a work-around, use the presence validator instead.
Related
I've set up three models: User, List, and UserList -- the latter being the join model between User and List, in a has_many_through relationship.
I'm trying to set up what I think should be fairly vanilla uniqueness constraints -- but it's not quite working. Would appreciate your guidance / advice please!
Technical details
I have 3 models:
class User < ApplicationRecord
has_many :user_lists
has_many :lists, through: :user_lists, dependent: :destroy
End
class List < ApplicationRecord
has_many :user_lists
has_many :users, through: :user_lists, dependent: :destroy
# no duplicate titles in the List table
validates :title, uniqueness: true
End
class UserList < ApplicationRecord
belongs_to :list
belongs_to :user
# a given user can only have one copy of a list item
validates :list_id, uniqueness: { scope: :user_id }
end
As you can see, I'd like List items to be unique, based on their title. In other words, if user Adam adds a List with title "The Dark Knight", then user Beatrice adding a List with title "The Dark Knight" shouldn't actually create a new List record -- it should just create a new / distinct UserList association, pointing to the previously created List item.
(Somewhat tangential, but I also added a unique index on the table since I understand this avoids a race condition)
class AddIndexToUserLists < ActiveRecord::Migration[5.2]
def change
add_index :user_lists, [:user_id, :list_id], unique: true
end
end
Here's where things are going wrong.
As user Adam, I log in, and add a new title, "The Dark Knight", to my list.
Here's the controller action (assume current_user correctly retrieves Adam):
# POST /lists
def create
#list = current_user.lists.find_or_create_by!(list_params)
end
This correctly results in a new List record, and associated UserList record, being created. Hurrah!
As Adam, if I try to add that same title "The Dark Knight", to my list again, nothing happens -- including no errors on the console. Hurrah!
However -- as user Beatrice, if I log in and now try to add "The Dark Knight" to my list, I now get an error in the console:
POST http://localhost:3000/api/v1/lists 422 (Unprocessable Entity)
My debugging and hypothesis
If I remove the uniqueness constraint on List.title, this error disappears, and Beatrice is able to add "The Dark Knight" to her list.
However, List then contains two records, both titled "The Dark Knight", which seems redundant.
As Adam, it seems like perhaps current_user.lists.find_or_create_by!(list_params) in my controller action is finding the existing "The Dark Knight" list associated with my current user, and realising it exists -- thereby not triggering the create action.
Then as Beatrice, it seems that the same controller action is not finding the existing "The Dark Knight" list item associated with my current user -- and therefore it tries to trigger the create action.
However, this create action tries to create a new List item with a title that already exists -- i.e. it falls foul of the List.rb model uniqueness validation.
I'm not sure how to modify that find_or_create_by action, or the model validations, to ensure that for Beatrice, a new UserList record / association is created -- but not a new List record (since that already exists).
It feels like maybe I'm missing something easy here. Or maybe not. Would really appreciate some guidance on how to proceed. Thanks!
I'm 99% certain that what's happening is current_user.lists.find_or_create_by will only search for List records that the user has a UserList entry for. Thus if the List exists but the current user doesn't have an association to it, it will try to create a new list which will conflict with the existing one.
Assuming this is the issue, you need to find the List independently of the user associations: #list = List.find_or_create_by(list_params)
Once you have that list, you can create a UserList record through the associations or the UserList model. If you're looking for brevity, I think you can use current_user.lists << #list to create the UserList, but you should check how this behaves if the user has a UserList for that list already, I'm not sure if it will overwrite your existing data.
So (assuming the << method works appropriately for creating the UserList) your controller action could look like this:
def create
#list = List.find_or_create_by!(list_params)
current_user.lists << #list
end
I'm making up this example for the sake of demonstrating my issue. Let's say we have the following models:
class Case < ApplicationRecord
has_many :clients
accepts_nested_attributes_for :clients
end
class Client < ApplicationRecord
belongs_to :case
has_many :attorneys
accepts_nested_attributes_for :attorneys
end
class Attorney < ApplicationRecord
belongs_to :client
end
I'd like to add the same new attorney to different clients.
If I use params like this to update a case:
{
"clients_attributes"=>{
"0"=>{
"id"=>"1",
"attorneys_attributes"=>{
"123"=>{
"name"=>"Joe"
}
}
},
"1"=>{
"id"=>"2",
"attorneys_attributes"=>{
"456"=>{
"name"=>"Joe"
}
}
}
}
}
Rails will create two new attorney records with the same name "Joe". I'd like to just create one record and have both clients refer to it. Is there some clever was to reference an as yet uncreated record?
One thing I could do is check the params on the server side for identical names and do some post-update work. Is there an easier way?
Rails check if it's same record via the ID. Therefore, if you're using nested fields, but want to reuse the attorney record, then you need to make sure their ID are the same.
On the other hand, why do you want to use nested field? Usually you have a nested form to update the information, but for your case here.. that would means to update the attorney information under any of the client model - and updating attorney under one client, would actually affect all the same attorney under other clients.
As #DollarChills put in the comments, why not use a select field for the attorney under client, and create a separate page to update the attorney information?
Update: auto-complete solution
For the fields you feel can uniquely identify the an attorney, create a auto-complete field, based on that field. Let's take name for example
If the user put in the name match some DB record, load it and let the user select. You'll also have a create new attorney option
If user selected the attorney, just load it into the form with corresponding ID, so that your app knows who to update.
Every time I create a new company record in rails, I need to add some default (blank) contact records at that company. Front Desk, Receiving, HR, IT and so on...they won't have any data in them besides the name, just a placeholder for the user to fill in later.
So, my company model has_many contacts, and contacts belong_to company. The contact records are static and the same for every new company that gets added, but I need to pre-populate the contacts table with data, so my users don't have to.
I've read a lot about seeding the database, but I won't be able to use the terminal every time a user dynamically creates a company, and it needs to be dynamically tied to that company, the records are not agnostic. Seeding doesn't seem to be the right thing. How should this be done?
you should use a before_save filter, which checks if an attribute is empty, and otherwise set it to the default.
Using a before_save will guard against deletions later on.
But be careful only to do this for fields which will never be empty.
class Company < ActiveRecord::Base
has_many :contacts
before_save :add_defaults
def add_defaults
contacts ||= Contact.default_list # this only sets it if it's nil
# you can implement Contact#default_list as a method, or as a scope in the contacts model
end
end
What about after_create callback in Company Model?
Smth like this:
class Company < ActiveRecord::Base
has_many :contacts
after_create :add_contacts
def add_contacts
contacts.create(name: "Some name", phone: "...", ....)
end
end
Although it notionally exists for generating test data, the FactoryGirl gem is very useful for this purpose. Use it in conjunction with the after_save approach mentioned here, and you'll have a nice place to centrally define your blank records.
New to Rails and Ruby and trying to do things correctly.
Here are my models. Everything works fine, but I want to do things the "right" way so to speak.
I have an import process that takes a CSV and tries to either create a new record or update an existing one.
So the process is 1.) parse csv row 2.) find or create record 3.) save record
I have this working perfectly, but the code seems like it could be improved. If ParcelType wasn't involved it would be fine, since I'm creating/retrieving a parcel FROM the Manufacturer, that foreign key is pre-populated for me. But the ParcelType isn't. Anyway to have both Type and Manufacturer pre-populated since I'm using them both in the search?
CSV row can have multiple manufacturers per row (results in 2 almost identical rows, just with diff mfr_id) so that's what the .each is about
manufacturer_id.split(";").each do |mfr_string|
mfr = Manufacturer.find_by_name(mfr_string)
# If it's a mfr we don't care about, don't put it in the db
next if mfr.nil?
# Unique parcel is defined by it's manufacturer, it's type, it's model number, and it's reference_number
parcel = mfr.parcels.of_type('FR').find_or_initialize_by_model_number_and_reference_number(attributes[:model_number], attributes[:reference_number])
parcel.assign_attributes(attributes)
# this line in particular is a bummer. if it finds a parcel and I'm updating, this line is superfulous, only necessary when it's a new parcel
parcel.parcel_type = ParcelType.find_by_code('FR')
parcel.save!
end
class Parcel < ActiveRecord::Base
belongs_to :parcel_type
belongs_to :manufacturer
def self.of_type(type)
joins(:parcel_type).where(:parcel_types => {:code => type.upcase}).readonly(false) unless type.nil?
end
end
class Manufacturer < ActiveRecord::Base
has_many :parcels
end
class ParcelType < ActiveRecord::Base
has_many :parcels
end
It sounds like the new_record? method is what you're looking for.
new_record?() public
Returns true if this object hasn’t been saved yet — that is, a record
for the object doesn’t exist yet; otherwise, returns false.
The following will only execute if the parcel object is indeed a new record:
parcel.parcel_type = ParcelType.find_by_code('FR') if parcel.new_record?
What about 'find_or_create'?
I have wanted to use this from a long time, check these links.
Usage:
http://rubyquicktips.com/post/344181578/find-or-create-an-object-in-one-command
Several attributes:
Rails find_or_create by more than one attribute?
Extra:
How can I pass multiple attributes to find_or_create_by in Rails 3?
I have two ActiveRecord classes. A simplified view of these classes:
class Account < ActiveRecord::Base
has_many :user_account_roles
end
class UserAccountRole < ActiveRecord::Base
belongs_to :account
# Has a boolean attribute called 'administrator'.
end
What I'm struggling with is that I'd like to be able to apply two validation rules to this:
* Ensuring that the last UserAccountRole cannot be removed.
* Ensuring that the last UserAccountRole that is an administrator cannot be removed.
I'm really struggling to understand the best way of achieving this kind of structural validation. I've tried adding a before_remove callback to the association, but I don't like that this has to throw an error which would need to be caught by the controller. I'd rather this be treated as 'just another validation'.
class Account < ActiveRecord::Base
has_many :user_account_roles, :before_remove => check_remove_role_ok
def check_remove_relationship_ok(relationship)
if self.user_account_relationships.size == 1
errors[:base] << "Cannot remove the last user from this account."
raise RuntimeError, "Cannot remove the last user from this account."
end
end
end
I don't think this makes any difference, but I'm also using accepts_nested_attributes_for.
Why not use a simple validation on Account?
class Account < ActiveRecord::Base
has_many :user_account_roles
validate :at_least_one_user_account_role
validate :at_least_one_administrator_role
private
def at_least_one_user_account_role
if user_account_roles.size < 1
errors.add_to_base('At least one role must be assigned.')
end
end
def at_least_one_administrator_role
if user_account_roles.none?(&:administrator?)
errors.add_to_base('At least one administrator role must be assigned.')
end
end
end
This way nothing needs to be raised, and the record won't be saved unless there's at least one role, and at least one administrator role. Thus when you re-render your edit form on error, this message will show up.
You could place the validation on UserAccountRole. If it is the only UserAccountRole associated with the Account, then it can't be deleted.
An easier solution may be to question an underlying assumption of your design. Why have UserAccountRole be an AR backed model? Why not just make it a plain ruby class? Is the end user going to dynamically define roles? If not, then you could greatly simplify your dilemma by making it a regular ruby class.