accepts_nested_attributes_for causing infinite loop in rails_admin - ruby-on-rails

I have two models that both accept_nested_attributes_for each other. I know this is causing issues with rails_admin but can't seem to figure out the appropriate command to exclude nested attributes in my configuration. Ideally I would like to exclude nested attributes for all models so that this problem does not happen again.

Branden,
You need to inform the bi-direction association using inverse_of:
Ex:
class User < ActiveRecord::Base
belongs_to :company, :inverse_of => :users
accepts_nested_attributes_for :company
end
For both sides of association:
class Company < ActiveRecord::Base
has_many :users, inverse_of: :company
accepts_nested_attributes_for :users, :allow_destroy => true
end
More information in the docs: http://guides.rubyonrails.org/association_basics.html#bi-directional-associations

Related

Trouble with validation check for has_many :through association; how to create?

Rails newbie here. I am having trouble with ensuring the presence of at least one "has_many :through" relationship in my users model.
I have:
class User < ApplicationRecord
has_many :company_users, dependent: :destroy
has_many :companies, through: :company_users
#validates :company_users, presence: true
end
class Company < ApplicationRecord
has_many :company_users, dependent: :destroy
has_many :users, through: :company_users
end
class CompanyUser < ApplicationRecord
belongs_to :user
belongs_to :company
end
I got the commented validates line from the excellent answers at:
Validate that an object has one or more associated objects
Rails 3: validate presence of at least one has many through association item
However when I implement this line, then doing the following in rails console:
c = Company.create
c.users.create!(email: "test#example.com")
gives
`raise_validation_error': Validation failed: Company users can't be blank (ActiveRecord::RecordInvalid)
From my newbie perspective, it seems like the HMT entry isn't created until after the user is created, which creates a validation error preventing the user in the first place! It's probably a very simple error, but how do I get the above behaviour to work with that validation in place?
I have tried setting inverse_of in a couple place, without any success.
And before it's mentioned, I'm purposefully using HMT instead of HABTM because I have additional attributes on the company_users model that will be set.
One option would be to change the validation to
validates :companies, presence: true
and then create the user with
User.create(companies: [Company.create])
Edit:
Adding inverse_of to the CompanyUser model should also work. The validation would be left as validate :company_users, presence: true:
class CompanyUser
belongs_to :user, inverse_of: :company_users
belongs_to :company
end

Rails 4.2 has_many through

Problem
I have two models a client and a user.
A client can have many administrators and a user can be the administrator of many clients.
I found a couple of people that suggest using has_many :through is the better way to model this relationship in my situation versus has_and_belongs_to_many.
User model
class V1::User < ActiveRecord::Base
has_many :administrators,
class_name: 'V1::ClientAdministrator'
has_many :clients,
through: :administrators
class_name: 'V1::Clients'
Client model
class V1::Client < ActiveRecord::Base
has_many :users,
class_name: "V1::User"
has_many :administrators,
through: :users,
class_name: "V1::ClientAdministrator"
validates :administrators,
length: { minimum: 1}
ClientAdministrator model
class V1::ClientAdministrator < ActiveRecord::Base
belongs_to :v1_user
belongs_to :v1_client
end
Demo using rails c
u = V1::User.create!(name: 'test_user')
c = V1::Client.new(name: 'test_client')
c.administrators << u
ActiveRecord::AssociationTypeMismatch: V1::ClientAdministrator(#70347494104080) expected, got V1::User(#70347494299440)
Before switching to has_many :through I was successfully using has_and_belongs_to_many:
class V1::Client < ActiveRecord::Base
has_and_belongs_to_many :administrators,
-> { uniq },
join_table: :v1_client_administrators,
foreign_key: "v1_client_id",
association_foreign_key: "v1_user_id",
class_name: "V1::User"
The problem with this approach was I was not able to do any validation on the association such as before_destroy make sure there is still one administrator. Additionally, it's likely that I'll add metadata to that relationship in the future.
The ASK
How can I get / set the administrators of the client?
Is there any way that client.administrators would be an array of users instead of forcing me to client.administrators.each { |admin| admin.user} to get access to the user? (If I eventually add metadata this probably can't doesn't make sense)
Is there a way to restrict the use of client.users in favor of client.administrators?
Do model concerns help here?
Is this the right approach for ensuring there is always at least one administrator?
I believe here is what you're looking for, please namespace appropriately:
class User
has_many :clients_users
has_many :clients, through: :clients_users
end
class Client
has_many :clients_users
has_many :administrators, through: :clients_users, source: :user
validates :adminstrators, presence: true # I think this should ensure at least one admin
end
class ClientsUser
belongs_to :client
belongs_to :user
end
Client.first.administrators # fetch all adminstrators
Client.first.adminstrators << User.first # add an administrator

Rails 4 many-to-many associations nightmare

I'm trying to set up a has_many :through association between three models: Trip, Location and LocationsTrip, but the associations just don't seem to be picking each other up.
I set up my associations:
class Trip < ActiveRecord::Base
belongs_to :user
has_many :locations_trips
has_many :locations, :through => :locations_trips
accepts_nested_attributes_for :locations_trips, :allow_destroy => :true
accepts_nested_attributes_for :locations, :allow_destroy => :true
end
class Location < ActiveRecord::Base
has_many :locations_trips, :dependent => :destroy
has_many :trips, :through => :locations_trips
end
class LocationsTrip < ActiveRecord::Base
belongs_to :locations
belongs_to :trips
end
When I run Trip.last.locations in the Rails console I get
NameError: uninitialized constant Trip::Locations
so I've obviously missed something crucial, but I feel as if I've been through every similar answer on here and can't see where I'm going wrong.
It's probably worth mentioning I'm trying to set this up with Trips accepting nested attributes for Locations so in the Trip form partial I've got a f.fields_for :locations do |builder| etc, but when I load up the page I get the same NameError as before.
If anyone can point me in the right direction I'd be massively grateful - I feel like I'm getting tunnel vision on this.
I'm using Ruby 2.1.2 and Rails 4.2.0.
It looks like you're using plural names for the join table belongs_to associations. Try changing to:
class LocationsTrip < ActiveRecord::Base
belongs_to :location
belongs_to :trip
end

How to define allow_destroy and :dependent => :destroy in Rails?

Given the following database model, how and where would you define the deletion relationships between the models? I figured out the basic table association setup but when I want to add dependencies to enable the deletion of nested objects I get lost.
Here is the relationship model I created.
class User < ActiveRecord::Base
has_many :studies
end
class Study < ActiveRecord::Base
has_many :internships
belongs_to :student, :class_name => "User", :foreign_key => "user_id"
belongs_to :subject
belongs_to :university, :class_name => "Facility", :foreign_key => "facility_id"
accepts_nested_attributes_for :subject, :university, :locations
end
class Subject < ActiveRecord::Base
has_many :studies
end
class Internship < ActiveRecord::Base
belongs_to :study
belongs_to :company, :class_name => "Facility", :foreign_key => 'facility_id'
accepts_nested_attributes_for :company, :study
end
class Facility < ActiveRecord::Base
has_many :internships
has_many :locations
has_many :studies
accepts_nested_attributes_for :locations
end
class Location < ActiveRecord::Base
belongs_to :facility
end
Where would you put :dependent => :destroy and :allow_destroy => true to enable the following scenarios? I do not want to confuse you. Therefore, I leave out my tryings.
Internship scenario: A user wants to delete an internship.
Its associated company (facility) can be deleted if the company is not related to another internship.
If so, the locations of the associated company can be deleted.
The related study will not be affected.
Study scenario: A user wants to delete a study.
Its associated subject can be deleted if no other study refers to this subject.
Its associated university (facility) can be deleted if no other study refers to this university.
Its associated internships can be deleted. The company can only be deleted if no other internship refers to it.
I am totally unsure whether I can add :dependent => :destroy only after has_one and has_many or also after belongs_to.
Edit: To simplify the problem please stick to the following (reduced) example implementation.
class Study < ActiveRecord::Base
belongs_to :subject
accepts_nested_attributes_for :subject, :allow_destroy => true
end
class Subject < ActiveRecord::Base
has_many :studies, :dependent => :destroy
end
In my view I provide the following link.
<%= link_to "Destroy", study, :method => :delete, :confirm => "Are you sure?" %>
The path is based on the named routes given by a restful configuration in routes.rb.
resources :studies
resources :subjects
The study will be deleted when I click the link - the subjects stays untouched. Why?
I think your relations are the wrong way around here...
The accepts_nested_attributes_for should be declared on the model that has_many for the model that it has_many of. Also, in your example, destroying the subject would enforce dependent_destroy on the many studies, not the other way around.
You can add :dependent => :destroy to all three but I'm not sure if that'll give you enough power to do the checks required before determining whether an associated object should be destroyed.
You have a few options.
Add a before_destroy callback on each model that raises an exception or stops the delete from occurring.
class Facility < ActiveRecord::Base
has_many :internships
has_many :locations
has_many :studies
def before_destroy
raise SomethingException if internships.any? || ...
# or
errors.add(...
end
end
or do it silently by overriding destroy
class Facility < ActiveRecord::Base
has_many :internships
has_many :locations
has_many :studies
def destroy
return false if internships.any || ...
super
end
end
Note: this is basically meant for guidance only and may not be the correct way of overriding destroy etc...

How to establish associations for a model with two belongs_to relationships?

I am building an application with the following model functions
Groups have many Users
Groups have many Expenses (each expense has a :name, :total, :added_by_user_id fields)
Expenses have many owings (1 for each user in the group)
Owings have an :amount and a :user_id, to reference which user the owing is referring
So far, I have set up the models as followings:
# user.rb
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name, :email, :password
has_many :memberships, :foreign_key => "member_id", :dependent => :destroy
has_many :groups, :through => :memberships
has_many :owings
end
# group.rb
class Group < ActiveRecord::Base
attr_accessible :name
has_many :memberships, :dependent => :destroy
has_many :members, :through => :memberships
has_many :expenses
end
# expense.rb
class Expense < ActiveRecord::Base
attr_accessible :total_dollars, :name, :owings_attributes, :added_by_user_id
belongs_to :group, :inverse_of => :expense
has_many :owings, :dependent => :destroy
end
# owing.rb
class Owing < ActiveRecord::Base
attr_accessible :amount_dollars, :user_id
belongs_to :expense, :inverse_of => :owings
belongs_to :user, :inverse_of => :owings
end
# NB - have left off memberships class (and some attributes) for simplicity
To create an expense, I'm using #group.expenses.build(params[:expenses]), where params come from a nested model form that includes attributes for the owings that need to be created. The params include the 'user_id' for each of the 'owing' instances for that expense.
I have two concerns:
Firstly - I've made 'user_id' accessible in the owings model, meaning that a malicious user can change who owes what in an expense (I think?). I don't know how to get around this, though, because the user needs to see the names of all the other members of the group when they fill out the expense/owings form.
Secondly - I've also made 'added_by_user_id' accessible in the expense model - I also wouldn't want malicious users to be able to change this, since this user_id has special edit/delete priveleges for the expense. Is there some clever way to make an expense 'belong_to' a User AND a group, and set both of these associations when creating WITHOUT having to make either an accessible attribute? If it helps, the 'added_by_user_id' can always be set to the current_user.
Any ideas? Very possible I'm missing something fairly fundamental here.
Thanks in advance!
PS. Long time listener, first time caller. Thanks to all of you for teaching me ruby on rails to date; this website is an incredible resource!
Have you thought about setting them dynamically?
dynamic attr-accessible railscast

Resources