where constraint on a related record - ruby-on-rails

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! :)

Related

Best way to update (create & delete) multiple associations (has_many :through)

I have what i feel could be a simple question, and i have this working, but my solution doesn't feel like the "Rails" way of doing this. I'm hoping for some insight on if there is a more acceptable way to achieve these results, rather than the way i would currently approach this, which feels kind of ugly.
So, lets say i have a simple has_many :through setup.
# Catalog.rb
class Catalog < ActiveRecord::Base
has_many :catalog_products
has_many :products, through: :catalog_products
end
# Products.rb
class Product < ActiveRecord::Base
has_many :catalog_products
has_many :catalogs, through: :catalog_products
end
# CatalogProduct.rb
class CatalogProduct < ActiveRecord::Base
belongs_to :catalog
belongs_to :product
end
The data of Catalog and the data of Product should be considered independent of each other except for the fact that they are being associated to each other.
Now, let's say that for Catalog, i have a form with a list of all Products, in say a multi-check form on the front end, and i need to be able to check/uncheck which products are associated with a particular catalog. On the form field end, i would return a param that is an array of all of the checked products.
The question is: what is the most accepted way to now create/delete the catalog_product records so that unchecked products get deleted, newly checked products get created, and unchanged products get left alone?
My current solution would be something like this:
#Catalog.rb
...
def update_linked_products(updated_product_ids)
current_product_ids = catalog_products.collect{|p| p.product_id}
removed_products = (current_product_ids - updated_product_ids)
added_products = (updated_product_ids - current_product_ids)
catalog_products.where(catalog_id: self.id, product_id: removed_products).destroy_all
added_products.each do |prod|
catalog_products.create(product_id: prod)
end
end
...
This, of course, does a comparison between the current associations, figures out which records need to be deleted, and which need to be created, and then performs the deletions and creations.
It works fine, but if i need to do something similar for a different set of models/associations, i feel like this gets even uglier and less DRY every time it's implemented.
Now, i hope this is not the best way to do this (ignoring the quality of the code in my example, but simply what it is trying to achieve), and i feel that there must be a better "Rails" way of achieving this same result.
Take a look at this https://guides.rubyonrails.org/association_basics.html#methods-added-by-has-many-collection-objects
You don't have to remove and create manually each object.
If you have already the product_ids array, I think this should work:
#Catalog.rb
...
def update_linked_products(updated_product_ids)
selected_products = Product.where(id: updated_product_ids)
products = selected_products
end
...
First,
has_many :products, through: :catalog_products
generate some methods for you like product_ids, check this under auto-generated methods to know more about the other generated methods.
so we don't need this line:
current_product_ids = catalog_products.collect{|p| p.product_id}
# exist in both arrays are going to be removed
will_be_removed_ids = updated_product_ids & product_ids
# what's in updated an not in original, will be appended
will_be_added_ids = updated_product_ids - product_ids
Then, using <<, and destroy methods which are also generated from the association (it gives you the ability to deal with Relations as if they are arrays), we are going to destroy the will_be_removed_ids, and append the will_be_added_ids, and the unchanged will not be affected.
Final version:
def update_linked_products(updated_product_ids)
products.destroy(updated_product_ids & product_ids)
products << updated_product_ids - product_ids
end

Maintain associations with act_as_paranoid

I have two models one of them is a User, and another Comments. Comments belong to User.
class User < ActiveRecord::Base
act_as_paranoid
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user
end
When I do user.delete in my controller I get the expected result of the deleted_at column being set and the record being hidden.
My problem is the Comment associations for the user are set to null. So now on the site it shows no user owning the comment. I'd like the comment to still show the user's name not be "None" or "Anonymous" etc.
Looking at the source on github https://github.com/goncalossilva/rails3_acts_as_paranoid/blob/rails3.2/lib/acts_as_paranoid/core.rb it seems to call run_callbacks which in turn results in Rails 3 falling back to Nullify default for associations.
In my case I just want the user account to be closed off when deleted. Not showing up in queries anymore so that Authlogic will deny them and the User index page won't show them. But still allowing everything a user owns to still be owned by them (since they may come back, etc.).
Is there a better way to do this then acts_as_paranoid?
Rather then go to the trouble of overriding the destroy method I created a close method that simply sets closed_at to a timestamp. If you set default scope to something like:
default_scope { where("closed_at IS NULL") }
Then the model won't show up to any queries including User.All. You can delete the scope to get a full query essentially I took these ideas from act_as_paranoid but much more simplified. The problem is that then even though the Comments still have user_id set, the default scope runs with any association load. So say
c = Comment.first
c.user
That will output nil if user_id is a closed account. In my case the easiest solusion was to remove default scoping and modify my Authlogic function to:
def self.find_by_username_or_email(login)
u = User.find(:first, :conditions => ["lower(username) = ?", login.downcase]) || User.find_by_email(login)
return u unless u.closed_at
end
This way closed accounts can't login. Anywhere I list out users in my views I used a hide_closed scope.
Not sure if this was the best most elegant solution. But for my purposes it works.

How to implement a last_modified_by (person) attribute on two unrelated models - Rails

I have a Record model and in order to edit this model, you must be logged in as an instance of Admin. I would like to have a column called last_modified_by which points to the Admin who last modified the Record. In the database, I was thinking it would be good in the records table to add a column that holds the Admin's id; however, the only way I know how to do that is with an association. These two models are not associated with each other so an association doesn't make a lot of sense. Is there any other way I might be able to accomplish this task without resorting to associations? Any advice would be much appreciated!
Hmm, I think the association is a good tool here. You might want to try to hack it somehow but I think nothing you can conjure up will ever be as good as an association via a foreign_key(also so fast). But perhaps you would like to name your association and do something like:
class Record < ActiveRecord::Base
belongs_to :culprit, :class_name => 'Admin', :foreign_key => 'last_modified_by'
end
or give it some more senseful naming?
You could create an Active Record before_save callback. The callback would save the admin's id into the last_modified_column. This would make sure the admin id is saved/updated each time there is a change to the model.
For example, assuming admin is #admin:
class Record < ActiveRecord::Base
before_save :save_last_modified
def save_last_modified
self.last_modified_column = #admin.id
end
As for getting #admin, you could employ a method similar to this, and set #admin = Admin.current (like User.current in the link) somewhere in the Record model.

new to rails : relationships not setup right?

Right now I have some functionality working but its really ugly and I know there's a better way to do it.
Here's what I have right now in my User model:
def attending
dayseventsusers=self.days_events_users
daysevents=Array.new
events=Array.new
dayseventsusers.each do |deu|
daysevents<<DaysEvent.find(deu['days_events_id'])
end
daysevents.each do |de|
events<<Event.find(de['event_id'])
end
return events
end
I'm trying to get a list of events for a certain group of dayseventsusers records but I have to move through several relationships to accomplish this. What I'd really like to do is something like self.days_events_users.days_event.event, but I don't know if it can be this simple assuming my relationships are setup correctly.
User model
has_many :days_events_users
DaysEventsUsers model
belongs_to :user
belongs_to :days_event
DaysEvent
has_many :days_events_users
belongs_to :event
The days_events_users object belong to a single days_event, which belongs to a single event. Any suggestions are welcome!
Without having tested this, does the following work:
dayseventusers.collect(&:daysevent).collect(&:event)

What is the best way of preventing the last record in a has_many collection being removed?

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.

Resources