where does rails get the queries for nested objects? - ruby-on-rails

I've got a nice 3-level nested-form using formtastic_cocoon (jquery), and now I want to be able to sort the 2nd set of items in the form.
I've got jQuery ui working no problem, so now to set and update the sort order in rails.
I started following the rails bits of railscasts sortable lists
http://asciicasts.com/episodes/147-sortable-lists
The form structure is User->Tasks->Location.
In my Task Model I set index to
def index
#task = Task.find(params[:id],:order=>'position')
end
def edit
#task = Task.find(params[:id],:order=>'position')
end
and I was expecting my console to see
... FROM 'tasks' WHERE ('users'.'id' = 12) ORDER BY ('position')
or something along those lines, but there is no order by output.
Is there somewhere else that I need to define this order?? Where does the nested_object get its relationship from? The model only?
My models are
class User < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :user
end

I changed the model to
class User < ActiveRecord::Base
has_many :tasks, :order=>'position'
end

You are using the wrong model, that id is of a user, so you have to do:
User.find(params[:id]).tasks(:order => 'position ASC')
Otherwise you are just getting the task with id = 12 and not the tasks whose user_id = 12

From the answer that you've given, I suspect the real issue lies not in the tasks controller. The default order you gave is great, but if you had some other order or filter requirements, the tasks model won't quite do it either.
I suspect you were actually in users#edit, or possibly a _form.html.erb, where it displays the form elements for each task. There might have been a #user.tasks.each {...} or similar loop block.
For a given user then, do: #user.tasks.order(:position). Or maybe you need open tasks: #user.tasks.where(:open=>true) etc.

Your code is slightly wrong here.
To find that user's tasks you would do this in your route:
User.find(params[:id]).tasks(:order=>'position')

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

Ruby on Rails Association build and assign 2 related associations

So I've got a User model, a Building model, and a MaintenanceRequest model.
A user has_many :maintenance_requests, but belongs_to :building.
A maintenance requests belongs_to :building, and belongs_to: user
I'm trying to figure out how to send a new, then create a maintenance request.
What I'd like to do is:
#maintenance_request = current_user.building.maintenance_requests.build(permitted_mr_params)
=> #<MaintenanceRequest id: nil, user_id: 1, building_id: 1>
And have a new maintenance request with the user and building set to it's parent associations.
What I have to do:
#maintenance_request = current_user.maintenance_requests.build(permitted_mr_params)
#maintenance_request.building = current_user.building
It would be nice if I could get the maintenance request to set its building based of the user's building.
Obviously, I can work around this, but I'd really appreciate the syntactic sugar.
From the has_many doc
You can pass a second argument scope as a callable (i.e. proc or lambda) to retrieve a specific set of records or customize the generated query when you access the associated collection.
I.e
class User < ActiveRecord::Base
has_many :maintenance_requests, ->(user){building: user.building}, through: :users
end
Then your desired one line should "just work" current_user.building.maintenance_requests.build(permitted_mr_params)
Alternatively, if you are using cancancan you can add hash conditions in your ability file
can :create, MaintenanceRequest, user: #user.id, building: #user.building_id
In my opinion, I think the approach you propose is fine. It's one extra line of code, but doesn't really increase the complexity of your controller.
Another option is to merge the user_id and building_id, in your request params:
permitted_mr_params.merge(user_id: current_user.id, building_id: current_user.building_id)
#maintenance_request = MaintenanceRequest.create(permitted_mr_params)
Or, if you're not concerned about mass-assignment, set user_id and building_id as a hidden field in your form. I don't see a tremendous benefit, however, as you'll have to whitelist the params.
My approach would be to skip
maintenance_request belongs_to :building
since it already belongs to it through the user. Instead, you can define a method
class MaintenanceRequest
belongs_to :user
def building
user.building
end
#more class stuff
end
Also, in building class
class Building
has_many :users
has_many :maintenance_requests, through: :users
#more stuff
end
So you can completely omit explicit building association with maintenance_request
UPDATE
Since users can move across buildings, you can set automatic behavior with a callback. The job will be done like you do it, but in a more Railsey way
class MaintenanceRequest
#stuff
before_create {
building=user.building
}
end
So, when you create the maintenance_request for the user, the building will be set accordingly

Rails Controller (events to activity)

I am trying to understand, what's difference between 1 and 2 line of codes.
Is it same code ? Thank You !
Activity : has_many :events
Event : belongs_to :activity
1)
#activity = Activity.find(params[:activity_id])
event = Event.new(event_params)
event.activity_id = #activity
2) Edited, 'events' supposed tobe pluralized.
#activity = Activity.find(params[:activity_id])
event = #activity.events.new(event_params)
Yeah, in general, the two approaches are basically doing the same things and will generate same results.
In scenario 1: You are finding an activity and initializing an event, and then associating the event to the activity.
In scenario 2: You are finding an activity and then initializing one of it's associated events using events association. Although it should be: #activity.events.new(event_params) NOT #activity.event.new(event_params) [Notice events should be plural as you have a has_many association]
If you call save in both cases, you will get the same result. Basically, when you will call: activity.events you will get the list of events associated with that activity. The above-created event will be in that list in both cases.
However, although both of the scenarios are doing the same thing, the second way is considered to be more Railsy way of doing things and hence a better practice.
Two blocks are doing the same. But they are not doing the more preferred way, they are doing differently. See my comment how they are doing differently. I explained line by line.
1)
#
# Finding the activity event
#activity = Activity.find(params[:activity_id])
#
# initialising event object from events parameters
event = Event.new(event_params)
# assigning activity in event, this will help building the
# association though its a manual process. Your ORM active record
# gives the best way to handle that. Your step 2 is
# something what is preferred.
event.activity_id = #activity
#
# Comment:
# This is not the best practice. Because its not utilising Rails's
# ORM active record
2)
# finding the activity
#activity = Activity.find(params[:activity_id])
event = #activity.events.new(event_params)
# Creating event using events association
# I believe your association name is different. it should
# be plural form events.
# it should be:
event = #activity.events.new(event_params)
#
# Comment: This is the preferred way.
# Although you can do more refactoring,
# like moving the #activity on any before action
# call back to ensure it is not define every time in
# your different different action.
No they're not the same lines of code.
They tell ActiveRecord to look up particular files in specific datatables, using the appropriate foreign key:
The has_many declaration will perform a query like this:
"SELECT * FROM `events` WHERE `event`.`id` IN ?", [activity.id]
It's pinging the events data table.
--
The belongs_to will pull data out of the parent table using the provided foreign_key:
"SELECT * FROM `activities` WHERE `activity`.`event_id` IN ?", [event.id]
It's important to note that you could also use this to get a similar result:
event_id = "SELECT * FROM `activites` WHERE `activity`.`id` IN ? LIMIT 1", ["1"]
"SELECT * FROM `activities` WHERE `activity`.`event_id` IN ?", [event_id]
IE you're essentially using data from the same table, whilst has_many pulls data from another table.
Although these look similar, they are very different in the background. The has_many association denotes the possibility of extra records in another data table; the belongs_to association has to have a "parent" object.
Thus, when using has_many / belongs_to, you have to understand which is the "parent" object. For example:
#app/models/post.rb
class Post < ActiveRecord::Base
has_many :comments #-> doesn't have to be any "comment" objects
end
#app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :post # -> only works if there is a "post" object
end
Hopefully that explains it a little clearer.
Also, you have to remember that Rails is built on top of a relational database.
This means that each time you use ActiveRecord or any of the adjoining functionality, you have to ensure that you understand what this means.
Relational databases work by taking a "foreign key" and applying it to a conjoining database. This allows your ORM (Object Relational Mapper) (in our case ActiveRecord) to pull the appropriate data from the other tables:
As such, all the associations you call within your application are basically ways to represent the above relational database setup.

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

Eager loading associated models in ActiveAdmin sql query

I've got an ActiveAdmin index page
ActiveAdmin.register Bill
And I am trying to display links to associated models
index do
column "User" do |bill|
link_to bill.user.name, admin_user_path(bill.user)
end
end
But I run into the N+1 query problem - there's a query to fetch each user.
Is there a way of eager loading the bills' users?
The way to do this is to override the scoped_collection method (as noted in Jeff Ancel's answer) but call super to retain the existing scope. This way you retain any pagination/filtering which has been applied by ActiveAdmin, rather than starting from scratch.
ActiveAdmin.register Bill do
controller do
def scoped_collection
super.includes :user
end
end
index do
column "User" do |bill|
link_to bill.user.name, admin_user_path(bill.user)
end
end
end
As noted in official documentation at http://activeadmin.info/docs/2-resource-customization.html
There is an answer on a different post, but it describes well what you need to do here.
controller do
def scoped_collection
Bill.includes(:user)
end
end
Here, you will need to make sure you follow scope. So if your controller is scope_to'ed, then you will want to replace the model name above with the scope_to'ed param.
The existing answers were right at the time, but ActiveAdmin supports eager loading with a much more convenient syntax now:
ActiveAdmin.register Bill do
includes :user
end
See the docs for resource customization
IMPORTANT EDIT NOTE : what follows is actually false, see the comments for an explanation. However I leave this answer where it stands because it seems I'm not the only one to get confused by the guides, so maybe someone else will find it useful.
i assume that
class Bill < ActiveRecord::Base
belongs_to :user
end
so according to RoR guides it is already eager-loaded :
There’s no need to use :include for immediate associations – that is,
if you have Order belongs_to :customer, then the customer is
eager-loaded automatically when it’s needed.
you should check your SQL log if it's true (didn't know that myself, i was just verifying something about :include to answer you when i saw this... let me know)
I've found scoped_collection loads all the entries, instead of just the ones for the page you are displaying. I think a better option is apply_collection_decorator that will only preload the items you are effectively displaying.
controller do
def apply_collection_decorator(collection)
collection.includes(:user)
end
end

Resources