I have two models Person and Address and a join table Persons::Address containing (person_id, address_id). If my Person object is p, then I do p.address_ids to get an array of address ids of that person.
Now, I have an after_save callback which uses p.address_ids. Let's say I had p.address_ids = [a,b,c]. I ran these two statements after this.
a.address_ids = [a,b,d]
a.save
In the after_save callback, if I do self.address_ids, I'll get [a,b,d]. I want to make an array A = [a,b,c,d] which should contain the elements of (last array + current array).uniq , what should I do?
Update :
In simple words, I want something like p.address_id_was (ActiveModel::Dirty) for associations like `p.address_ids'.
It's a many-to-many relationship, and I think you're doing it wrong.
The join table should be called addresses_people you can use this migration to generate it:
class CreateJoinTableAddressPerson < ActiveRecord::Migration
def change
create_join_table :addresses, :people
add_index :addresses_people, [:address_id, :person_id], unique: true
end
end
I've put a unique index on the composite key to prevent the same relationship being entered more than once.
Make sure your Person class has has_and_belongs_to_many :addresses and your Address class has has_and_belongs_to_many :people
Let's suppose you have a Person object p and this person has two addresses a and b you can get the collection of p's addresses by saying p.addresses and you can get the collection of people that live at address a by saying a.people
If you want to add more addresses to p
p.addresses << c
p.addresses << d
if you add an address that is already related to p it won't be duplicated, nothing will change.
Try this:
a.address_ids = a.address_ids|[a,b,d]
what if you store old address_ids in before_save callback, and then use it with new ones to create joined array?
Related
I have below structure:
class Transaction < ApplicationRecord
belongs_to :transactionable, polymorphic: true
end
class TransactionSale < ApplicationRecord
has_many :transactions, as: :transactionable
end
class TransactionRental < ApplicationRecord
has_many :transactions, as: :transactionable
end
I want to get all the linked transactions (sale & rental) by querying the Transaction table only.
For example:
Transaction.includes(:transactionable).where(project_id: project_id).map { |txn| txn.transactionable }
The above query returns both TransactionSale and TransactionRental objects combined and that is the exact result I wanna achieve. The only problem here is it returns a ruby array instead of ActiveRecord::Relation so I can't sort the transactions further in a single query or use other active record methods.
I have read all other answers that suggest plucking the ids and then apply where which is not possible here coz I don't know whether the table is rental or sale.
Is there any way to achieve the same result without losing the ActiveRecord relation?
No nice/easy way, no. You could do it by creating the SQL and running a UNION as a subquery but that's pretty ugly.
Apparantly i solved it myself,
# Get id and classes of all transaction types
locks = Transaction.where(project_id: self.id).pluck(:transactionable_id, :transactionable_type)
# Init empty active record relation
final = Transaction.none
# convert id and classes array to hash to group on polymorphic classes
locks.group_by { |s| s[1] }.each_pair do |k,v|
# extracts ids to make query
ids = v.map { |d| d[0] }
# make query from respective model
data = eval(k).where(id: ids)
# merge the array returned above with empty relation and rewrite
final = final.or(data)
end
The final contains the active record relation of all polymorphic records.
I have a one-to-many relationship between the classes P(parent) and C(childs).
Table C has a unique composite index {p_id, somerow}.
Having 2 objects of class P (p1 and p2), I want to combine them into one, doing this through
p2.childs.update_all (parent: p1), but I get a rollback of the transaction, because the uniqueness of the composite index is violated. However, from the point of view of internal logic, this situation is not an error, and a duplicate entry, instead of changing the parent, must be destroyed.
What is the most correct way to solve the problem?
P.S. The number of requests to the database is critical.
P.P.S. The number of children in the relation can exceed the value in 1k records.
If you are ready to validate records by Rails, which means execute a separate query to validate each record, here is a straightforward way to do it:
class C < ActiveRecord::Base
validates_uniqueness_of :somerow, scope: :p_id
belongs_to :p
end
class P < ActiveRecord::Base
has_many :childs
def merge_sibling(p2)
p2.childs.each do |c|
c.p_id = self.id
c.valid? ? c.save : c.destroy
end
p2.destroy
end
end
p1.merge_sibling p2
Kind of new to Ruby/Rails, coming from c/c++, so I'm doing my baby steps.
I'm trying to find the most elegant solution to the following problem.
Table A, among others has a foreign key to table B (let's call it b_id), and table B contains a name field and a primary (id).
I wish to get a list of object from A, based on some criteria, use this list's b_id to access Table B, and retrieve the names (name field).
I've been trying many things which fail. I guess I'm missing something fundamental here.
I tried:
curr_users = A.Where(condition)
curr_names = B.where(id: curr_users.b_id) # fails
Also tried:
curr_names = B.where(id: curr_users.all().b_id) # fails, doesn't recognize b_id
The following works, but it only handles a single user...
curr_names = B.where(id: curr_users.first().b_id) # ok
I can iterate the curr_users and build an array of foreign keys and use them to access B, but it seems there must be more elegant way to do this.
What do I miss here?
Cheers.
Assuming you have following models:
class Employee
belongs_to :department
end
class Department
has_many :employees
end
Now you can departments based on some employee filter
# departments with employees from California
Department.include(:employees).where(:employees => {:state => "CA"}).pluck(:name)
For simplicity, let's take an example of Article and Comments, instead of A and B.
A Comment has a foreign key article_id pointing at Article, so we can setup a has_many relationship from Article to Comment and a belongs_to relationship from Comment to Article like so:
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :article
end
Once you have that, you will be able do <article>.comments and Rails will spit out an array of all comments that have that article's foreign key. No need to use conditionals unless you are trying to set up a more complicated query (like all comments that were created before a certain date, for example).
To get all the comment titles (names in your example), you can do <article>.comments.map(&:title).
I have two model classes: Cars and Customers,
Model Cars:
class car < ActiveRecord::Base
#car has attribute :town_code
has_many :customers
end
Model Customers:
class customer < ActiveRecord::Base
# customer has attribute :first_name, :last_name
belongs_to :car
end
In my controller, I have the following code:
my_customer = Customer.find_all_by_first_name('John')
p my_customer.last_name
p my_customer.car_id
But I got no attribute 'car_id' error, I also got no attribute 'last_name' error.
---Question 1:---
I checked my database, I do have 'car_id' and 'last_name' columns on my customer table. Why I can not access them in the way how my controller code does?
---Question 2:---
but the code : my_customer.map(&:car_id) is working for accessing car_id, however, I do not quite understand the code .map(&:car_id), what does it do? Can anyone explains to me?
The reason you aren't able to do my_customer.last_name is that my_customer is not a Customer here but an array of Customers, since you did find_all. That's also why my_customer.map(&:car_id) works. What that bit of code means is: For each object in the array my_customer, call the method car_id and insert the results into a new array -- and return that new array.
If customer belongs to car, you need a car_id in the customer table (which corresponds to an id column in the car table). Also, you shouldn't have last_name in the car table, but rather in the customer table.
It sounds like you may need to step back and gain a better understanding of ActiveRecord associations. It's not clear to me why a customer would belong_to a car, anyway.
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