Failing to update foreign keys - ruby-on-rails

I have the following association:
class Tenant < ApplicationRecord
belongs_to :landowner
end
and
class Landowner < ApplicationRecord
has_many :tenants
end
In my webapp, when I initialise a Tenant object, I add a default Landowner, which I have already stored with id of 0:
# my initilisation
new_tenant = Tenant.create(landowner_id: 0)
The problem I'm running into is that I want to update this Tenant object's landowner to a new landowner, say with id of 1. I'm trying to do this through the console with:
# I only have one tenant, and I made sure the last landowner is a new different landowner
Tenant.first.landowner = Landowner.last
Tenant.first.save!
I don't get any errors but the change does not persist. I have also tried:
Tenant.first.update({:landowner_id => 1})
but this does not work either (no error, but changes do not persist). Can someone help me with this?

You are reinitialising the object again by calling first() on Tenant for the second time, try this instead
tenant = Tenant.first
tenant.landowner = Landowner.last
tenant.save!
This will either save or throw a validation error.
Hope it helps!

Related

Caching of Classes / Object lists in Ruby on Rails

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.

Rails get title of associated resource by id

I have 2 models(Clients and Projects) that are connected like this:
class Project < ActiveRecord::Base
belongs_to :cliente
end
class Cliente < ActiveRecord::Base
has_many :projects
end
Projects have a :cliente_id column in its schema, so if I do:
Project.cliente_id I will get the cliente_id correctly.
My doubt is, I want to get the client name from it's id, so I need something like:
Project.cliente_id.name
Which is the correct way to retrieve this info?
You find associated objects through the association:
project = Project.find(1) # Returns the full `project` object
project.cliente # Returns the full `cliente` object
project.cliente.name # Returns just the `name` attribute
project.cliente_id == project.cliente.id # Returns true
You can get the complete Cliente object with project.cliente (note that the _id is not used). So you can use it like a regular Cliente; for example, to get name just do:
project = Project.find(1)
project.cliente.name

where constraint on a related record

I'm not getting a concept (nothing new there) on how to scope a Active Record query. I want to only receive the records where there is a certain condition in a related record. The example I have happens to be polymorphic just in case that is a factor. I'm sure there is somewhere where this is explained but I have not found it for whatever reason.
My Models:
class User < ActiveRecord::Base
belongs_to :owner, polymorphic: true
end
class Member < ActiveRecord::Base
has_one :user, as: :owner
end
I want to basically run a where on the Member class for related records that have a certain owner_id/owner_type.
Lets say we have 5 Members with ids 1-5 and we have one user with the owner_id set to 3 and the owner_type set to 'Member'. I want to only receive back the one Member object with id 3. I'm trying to run this in Pundit and thus why I'm not just going at it form the User side.
Thanks for any help as always!!!
Based on your comment that you said was close I'd say you should be able to do:
Member.joins(:user).where('users.id = ?', current_user.id)
However based on how I'm reading your question I would say you want to do:
Member.joins(:user).where('users.owner_id = ?', current_user.id)
Assuming current_user.id is 3.
There may be a cleaner way to do this, but that's the syntax I usually use. If these aren't right, try being a little more clear in your question and we can go from there! :)

Correct way to create or update with multiple belongs_to in Rails

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?

Rails: Has and belongs to many (HABTM) -- create association without creating other records

Spent all day on Google, but can't find an answer. :\
I have a HABTM relationship between Users and Core_Values.
class CoreValue < ActiveRecord::Base
has_and_belongs_to_many :users
class User < ActiveRecord::Base
has_and_belongs_to_many :core_values
In my controller, I need to do two separate things:
If a CoreValue does not exist, create a new one and associate it with a given user id, and
Assuming I know a particular CoreValue does exist already, create the association without creating any new CoreValues or Users
For # 1, I've got this to work:
User.find(current_user.id).core_values.create({:value => v, :created_by => current_user.id})
This creates a new CoreValue with :value and :created_by and creates the association.
For # 2, I've tried a few things, but can't quite seem to create the association ONLY.
Thanks for your help!
You can do this in a two-step procedure, using the very useful find_or_create method. find_or_create will first attempt to find a record, and if it doesn't exist, create it. Something like this should do the trick:
core_value = CoreValue.find_or_create_by_value(v, :created_by => current_user.id)
current_user.core_values << core_value
Some notes:
The first line will find or create the value v. If it doesn't exist and is created, it will set the created_by to current_user.id.
There's no need to do User.find(current_user.id), as that would return the same object as current_user.
current_user.core_values is an array, and you can easily add another value to it by using <<.
For brevity, the following would be the same as the code example above:
current_user.core_values << CoreValue.find_or_create_by_value(v, :created_by => current_user.id)
Add a method in your user model:
class User < ActiveRecord::Base
def assign_core_value(v)
core_values.find_by_name(v) || ( self.core_values <<
CoreValue.find_or_create_by_name(:name => v, :created_by => self)).last
end
end
The assign_core_value method meets requirement 1,2 and returns the core value assigned to the user (with the given name).
Now you can do the following:
current_user.assign_core_value(v)
Note 1
Method logic is as follows:
1) Checks if the CoreValue is already associated with the user. If so no action is taken, core value is returned.
2) Checks if the CoreValue with the given name exists. If not creates the CoreValue. Associates the core value(created/found) with the user.
Active Record already gives you a method. In your case,
val = CoreValue.find_by_value(v)
current_user.core_values << val
You can also pass a number of objects at ones this way. All the associations will be created
Check this for more information

Resources