Rails - Object in has_many relation is not getting updating - ruby-on-rails

I am having a table called Groups, where Groups and Sub-Groups are saved. A group has_many sub-group. Below is the code:
groupone.rb (one of the main group)
class GroupOne < BaseGroup
belongs_to :parent, class_name: 'GroupOne'
has_many :sub_group_one, autosave: true, dependent: :destroy, inverse_of: :groupone
end
reports_controller.rb
class ReportsController < ActionController::Base
def process_report
current_record = load_from_xml(xml_path)
current_group = current_record.last
base_report_group = find_or_create_base_group(current_group)
process_sub_group(current_group, base_report_group)
base_report_group.save
end
def process_sub_group(current_group, base_report_group)
if current_group.sub_group_one.present?
current_group.sub_group_one.each do |sub_group|
sgroup = base_report_group.sub_group_one.find_or_initialize_by(group_type_id: sub_group.group_type, serial_num: sub_group.serial_num)
sgroup.attributes = {name: "Rob", age: 12}
end
end
end
end
The above code is creating new GroupOne and many sub_group_one records with out any issues but when I try to update the existing sub_group_one values, they are not getting updated. For example {name: "Rob", age: 12} is not getting updated to any of the sub-group record. I noticed that the new attributes are assigned to sgroup during the current sub_group iteration and once all the sub_group iteration are completed, when I do binding.pry for base_report_group.sub_group_one it shows the old record and this is the issue.
Can any one please help me to fix this?

Related

Rails join through 2 other tables

I am trying to join tables to get an object.
I have these models:
class Company < ApplicationRecord
has_many :users
end
class Claim < ApplicationRecord
has_many :uploads, dependent: :destroy
validates :number, uniqueness: true
belongs_to :user, optional: true
end
class User < ApplicationRecord
belongs_to :company
has_many :claims
end
Basically I want to select all claims that belong to users that belong to a company.
Somethings I have tried:
(This works but is terrible and not the rails way)
#claims = []
#company = Company.find(params[:id])
#users = #company.users
#users.each do |u|
u.claims.each do |c|
#claims.push(c)
end
end
#claims = #claims.sort_by(&:created_at)
if #claims.count > 10
#claims.shift(#claims.count - 10)
end
#claims = #claims.reverse
This is close but doesn't have all the claim data because its of the user:
#claims = User.joins(:claims, :company).where("companies.id = users.company_id").where("claims.user_id = users.id").where(company_id: params[:id]).order("created_at DESC").limit(10)
I tried this but keep getting an error:
#claims = Claim.joins(:user, :company).where("companies.id = users.company_id").where("claims.user_id = users.id").where(company_id: params[:id]).order("created_at DESC").limit(10)
error: ActiveRecord::ConfigurationError (Can't join 'Claim' to association named 'company'; perhaps you misspelled it?)
Any ideas what I should do or change?
Based on your relations, you should use
Claim.joins(user: :company)
Because the Company is accessible through the relation Claim <> User.
If you wanted to join/preload/include/eager load another relation, let's say if Claim belongs_to :insurance_company, then you would add it like this:
Claim.joins(:insurance_company, user: :company)
Similar questions:
Join multiple tables with active records
Rails 4 scope to find parents with no children
That being said, if you want to
select all claims that belong to users that belong to a company
Then you can do the following:
Claim
.joins(:user) # no need to join on company because company_id is already on users
.where(company_id: params[:id])
.order(claims: { created_at: :desc })
.limit(10)
Tada!

How to detect changes in has_many through association?

I have the following models.
class Company < ApplicationRecord
has_many :company_users
has_many :users, :through => :company_users
after_update :do_something
private
def do_something
# check if users of the company have been updated here
end
end
class User < ApplicationRecord
has_many :company_users
has_many :companies, :through => :company_users
end
class CompanyUser < ApplicationRecord
belongs_to :company
belongs_to :user
end
Then I have these for the seeds:
Company.create :name => 'Company 1'
User.create [{:name => 'User1'}, {:name => 'User2'}, {:name => 'User3'}, {:name => 'User4'}]
Let's say I want to update Company 1 users, I will do the following:
Company.first.update :users => [User.first, User.second]
This will run as expected and will create 2 new records on CompanyUser model.
But what if I want to update again? Like running the following:
Company.first.update :users => [User.third, User.fourth]
This will destroy the first 2 records and will create another 2 records on CompanyUser model.
The thing is I have technically "updated" the Company model so how can I detect these changes using after_update method on Company model?
However, updating an attribute works just fine:
Company.first.update :name => 'New Company Name'
How can I make it work on associations too?
So far I have tried the following but no avail:
https://coderwall.com/p/xvpafa/rails-check-if-has_many-changed
Rails: if has_many relationship changed
Detecting changes in a rails has_many :through relationship
How to determine if association changed in ActiveRecord?
Rails 3 has_many changed?
There is a collection callbacks before_add, after_add on has_many relation.
class Project
has_many :developers, after_add: :evaluate_velocity
def evaluate_velocity(developer)
#non persisted developer
...
end
end
For more details: https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Association+callbacks
You can use attr_accessor for this and check if it changed.
class Company < ApplicationRecord
attr_accessor :user_ids_attribute
has_many :company_users
has_many :users, through: :company_users
after_initialize :assign_attribute
after_update :check_users
private
def assign_attribute
self.user_ids_attribute = user_ids
end
def check_users
old_value = user_ids_attribute
assign_attribute
puts 'Association was changed' unless old_value == user_ids_attribute
end
end
Now after association changed you will see message in console.
You can change puts to any other method.
I have the feelings you are asking the wrong question, because you can't update your association without destroy current associations. As you said:
This will destroy the first 2 records and will create another 2 records on CompanyUser model.
Knowing that I will advice you to try the following code:
Company.first.users << User.third
In this way you will not override current associations.
If you want to add multiple records once try wrap them by [ ] Or ( ) not really sure which one to use.
You could find documentation here : https://guides.rubyonrails.org/association_basics.html#has-many-association-reference
Hope it will be helpful.
Edit:
Ok I thought it wasn't your real issue.
Maybe 2 solutions:
#1 Observer:
what I do it's an observer on your join table that have the responsability to "ping" your Company model each time a CompanyUser is changed.
gem rails-observers
Inside this observer call a service or whatever you like that will do what you want to do with the values
class CompanyUserObserver < ActiveRecord::Observer
def after_save(company_user)
user = company_user.user
company = company_user.company
...do what you want
end
def before_destroy(company_user)
...do what you want
end
end
You can user multiple callback in according your needs.
#2 Keep records:
It turn out what you need it keep records. Maybe you should considerate use a gem like PaperTrail or Audited to keep track of your changes.
Sorry for the confusion.

Ruby on Rails relationship model

In Ruby on Rails 4, how do you create a many-to-many relationship inside a relationship model for a friends list such as Facebook using the has_many :through ... syntax ?? I'm a newbie and currently learning Ruby on Rails 4. I have looked at this link.
But still have a hard time grasping it.
you will need a join table that references both sides of the relations
let us say you have an relation Post and another relation Category with a many to many relationship between them you need a join table to be able to represent the relationship.
migration for a join table would be
class CreateCategoriesPosts < ActiveRecord::Migration
def change
create_table :categories_posts do |t|
t.integer :category_id
t.integer :post_id
t.timestamps
end
add_index :categories_posts, [:category_id, :post_id]
end
end
and in the models/post.rb
Class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
end
and in the models/category.rb
Class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
more here:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
I think #RAF pretty much nailed it. But to use the OP's example:
class User < ActiveRecord::Base
has_and_belongs_to_many :users_list
end
class UsersList < ActiveRecord::Base
has_and_belongs_to_many :users
end
Although at first it might seem like a User should have only one list of friends (UsersList), that might not always be the case. Think of types within the UserList model, such as: 'close friends', 'work friends', 'all friends' for example.
My advice: dig into the Rails guides. This is a concept worth learning and truly understanding (which I'm still doing :).
many-to_many relationships are a simple concept, but complex when using the database because of the way databases work. A person could have 1 to N different friends, which means that a single entry for a database would need a dynamic amount of memory for each entry, which in the db world is a no-no. So instead of creating a list of friends you would have to make a table that represents the links between friends, for example:
friendship.rb
class Friendship < ActiveRecord::Base
belongs_to :friend, foreign_key: 'friend_A' # this entry has a field called 'friend_A'
belongs_to :friend, foreign_key: 'friend_B' # this entry has a field called 'friend_B'
end
These links will represent your network of friends. However, as the two previous answers have mentioned, Rails has some nifty magic, "has_and_belongs_to_many", which will do this for you.
NOTICE: The problem here is that in my StatusesController, in the index action, the #relationship object only gets the statuses of all your friends, but does not get your own statuses. Is there a better way of approaching this? I am trying to create a view to view all statuses of users that are your friends, and your own statuses too, and so far, I can't seem to figure out how to order it chronologically, even if in my status model, i included "default_scope -> { order(created_at: :desc) } ". Any advice would be deeply appreciated
class User < ActiveRecord::Base
has_many :relationships
has_many :friends, :through => :relationships
has_many :inverse_relationships, class_name: 'Relationship', foreign_key: 'friend_id'
has_many :inverse_friends, through: 'inverse_relationships', :source => :user end
#
class Relationship < ActiveRecord::Base
# before_save...
belongs_to :user
belongs_to :friend, class_name: 'User'
end
#
class RelationshipsController < ApplicationController
def friend_request
user_id = current_user.id
friend_id = params[:id]
if Relationship.where( user_id: user_id, friend_id: friend_id, accepted: false).blank?
Relationship.create(user_id: user_id, friend_id: friend_id, accepted: false)
redirect_to user_path(params[:id])
else
redirect_to user_path(params[:id])
end
end
def friend_request_accept
# accepting a friend request is done by the recipient of the friend request.
# thus the current user is identified by to_id.
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
if Relationship.exists?(relationship) and relationship.accepted == false
relationship.update_attributes(accepted: true)
end
redirect_to relationships_path
end
def friend_request_reject
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
relationship.destroy
redirect_to relationships_path
end
################################
def index
#relationships_pending = Relationship.where(friend_id: current_user.id, accepted: false)
end
end
#
class StatusesController < ApplicationController
def index
#status = Status.new
#relationship = Relationship.where('friend_id = ? OR user_id = ?', current_user.id, current_user.id).
where( accepted: true)
end
def new
#status = Status.new
end
end
#

Update attributes on has_many through associations and working with the unsaved object

This has something to do with my last quesion about unsaved objects, but now it is more about a specific problem how to use rails.
The models I have are:
class User < ActiveRecord::Base
has_many :project_participations
has_many :projects, through: :project_participations, inverse_of: :users
end
class ProjectParticipation < ActiveRecord::Base
belongs_to :user
belongs_to :project
enum role: { member: 0, manager: 1 }
end
class Project < ActiveRecord::Base
has_many :project_participations
has_many :users, through: :project_participations, inverse_of: :projects
accepts_nested_attributes_for :project_participations
end
With this models, when I create a new project I can do it by a form (fields_for etc) and then I can call update_attributes in the controller. So if I have users in the database already, I can do this:
u = Users.create # save one user in database (so we have at least one saved user)
p = Project.new
# add the user to the project as a manager
# the attributes could come from a form with `.fields_for :project_participations`
p.update_attributes(project_participations_attributes: [{user_id: u.id, role: 1}])
=> true
This works fine until I want to do something with the users of a project. For example I want add a validations that there must be at least one user for a project:
class Project < ActiveRecord::Base
...
validates :users, presence: true # there must be at least one user in a project
...
end
This now gives:
u = Users.create
p = Project.new
p.update_attributes(project_participations_attributes: [{user_id: u.id, role: 1}])
=> false
p.errors
=> #<ActiveModel::Errors:... #base=#<Project id: nil>, #messages={:users=>["can't be blank"]}>
p.users
=> #<ActiveRecord::Associations::CollectionProxy []>
p.project_participations
=> #<ActiveRecord::Associations::CollectionProxy [#<ProjectParticipation id: nil, user_id: 1, project_id: nil>]>
So on unsaved projects the .users is empty. This already bugs me (see my last quesion about unsaved objects). But in this case I can of course now work around this by doing validates :project_participations, presence: true instead of validates :users, presence: true and it should mean the same.
But this would mean I should never use the .users method (in any helper, model, view, ...) unless I am totally sure that I work with a saved object. Which in fact renders the .users method unusable (like it does with the validation of user`s presence).
If I call update_attributes like this, the validations works and it saves:
p.update_attributes(users: [u])
With this it creates the project_participation by itself so p.users works as expected. But here I cannot set any data like role for project_participation of that user.
So my questions are: Can I make the .users method work whether or not the object is saved (I think not)? But then, how can I add users to a unsaved project as a manager/member and work with the unsaved project?
I hope my problem is clear.
I think I understand you question, and you're correct in assuming that you cannot use the .users method whether or not the project model is saved. The reason for this is that in defining an association in Project (ie. has_many :users, through: :project_participations, inverse_of: :projects) you're telling rails to read the users attribute out of the database via the project_participations join table and when you haven't saved the project you have nothing to read out of the database.
In order to add a User to your project in a particular role you will need to create a new ProjectParticipation model which you will then associate to your project. If you then remove the users association and write your own users method you should be able to access your collection of users regardless of whether or not the project has been saved.
class Project < ActiveRecord::Base
has_many :project_participations
...
def users
project_participations.collect { |pp| pp.user }
end
end
Then something like:
u = Users.create
p = Project.new
pp = ProjectParticipation.new({user: u, project: p, role: 1})
p.project_participations << pp
p.users
Hopefully that helps.

Association not working

I have three models:
Department
class Department < ActiveRecord::Base
has_many :patients, :dependent => :destroy
has_many :waitingrooms, :dependent => :destroy
end
Waitingroom with fields patient_id:integer and department_id:integer
class Waitingroom < ActiveRecord::Base
belongs_to :patient
end
Patient with department_id:integer
class Patient < ActiveRecord::Base
belongs_to :department
has_many :waitingrooms
end
I save a waitingroom after a patient was in the waitingroom! So now i tried to retrieve the patients who where in the the waitingroom of the department:
def index
#waited = #current_department.waitingrooms.patients
end
Somehow it didnt worked it returned this error:
undefined method `patients' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Waitingroom:0x374c658>
But this worked: What did i wrong? Thanks!
def index
#waited = #current_department.waitingrooms
end
You can't invoke an association on a collection. You need to invoke it on a specific record. If you want to get all the patients for a set of waiting rooms, you need to do this:
def index
rooms = #current_department.waitingrooms
#waited = rooms.map { |r| r.patients }
end
If you want a flat array, you could (as a naive first pass) use rooms.map { |r| r.patients }.flatten.uniq. A better attempt would just build a list of patient ids and fetch patients once:
#waited = Patient.where(id: rooms.pluck(:patient_id).uniq)

Resources