I have a many to many polymorphic relationship:
class DocumentTask < ActiveRecord::Base
belongs_to :task
belongs_to :document, polymorphic: true
end
class Task < ActiveRecord::Base
has_many :documents, through: :document_tasks
end
class Contact < ActiveRecord::Base
has_many :tasks, though: :document_tasks, as: :document
end
class Service < ActiveRecord::Base
has_many :tasks, through: :document_tasks, as: :document
end
I have a URL that looks like this that sends information to the server:
/contacts/1/tasks?status_id_is=1&user_id_is=1
But sometimes it can look like this (since there is a polymorphic relationship):
/services/1/tasks?status_id_is=1&user_id_is=1
In my controller, I need to get the object of that polymorphic relationship. Typically, I can do the following:
document_id = params.detect {|name,_| name =~ /(.+)_id/}
#document = document_id[0].gsub("_id","").capitalize.constantize.find(document_id[1])
However, when the url contains a query string, this won't work because status_id_is and user_id_is will match the regular expression. An obvious solution would be to change the regex to /^(.+)_id$/ but who knows how many other use cases may arise.
Is there a nice way to achieve what I want? For example, something in the request object like request.association_parameter or params.association_parameter.
For filtering on a collection--like a search or the params you have to filter tasks--I can suggest an alternative approach which is to use params[:q] instead for filtering.
This is in line with "some" conventions for API filtering, like how ransack (gem) and ElasticSearch use q or query params, which is a wrapper param for scoping.
Equivalently, if following this approach, yours will look like below
/contacts/1/tasks?q[status_id_is]=1&q[user_id_is]=1
In this way, you will now have no problems looping in your params.detect because the status_id_is and user_id_is will be inside params[:q] and therefore is one-level deeper in the hash, which the params.detect loop will already no longer take into account. Thus any MODEL_id params will work for the polymorphic route.
Related
I've got two models with a has_many / has_many relationship. I have a variable exp_ids which is an array of integers representing the id's of some ExperienceLevel records. I need to write a query that will select all JobDescriptions that have an ExperienceLevel with one of those ids.
The query must work on an existing ActiveRelation object called job_descriptions, which is being passed through some flow controls in my controller to filter the results based on my params.
I've tried these queries below and some other variations, but with little success. As far as I can tell, ActiveRecord thinks that experience_levels is an attribute, which is causing it to fail.
job_descriptions.where(experience_levels: exp_ids)
job_descriptions.joins(:experience_levels).where(experience_levels.id: exp_ids)
job_descriptions.joins(:experience_levels).where(experience_levels: exp_ids)
job_descriptions.joins(:experience_levels).where("experience_levels.id IN exp_ids")
job_descriptions.includes(:experience_levels).where("experience_levels.id = ?", exp_ids).references(:experience_levels)
Here are my models:
class JobDescription < ActiveRecord::Base
has_many :job_description_experience_levels
has_many :experience_levels, through: :job_description_experience_levels
end
class JobDescriptionExperienceLevel < ActiveRecord::Base
belongs_to :job_description
belongs_to :experience_level
end
class ExperienceLevel < ActiveRecord::Base
has_many :job_description_experience_levels
has_many :job_descriptions, through: :job_description_experience_levels
end
I'm not sure if what I want to do is even possible. I've used a similar approach for another job_description filter where I selected the company_id, but in the case, company_id was an attribute of JobDescription.
Try this:
job_descriptions.joins(:job_description_experience_levels).where(job_description_experience_levels: { experience_level_id: exp_ids })
job_descriptions.joins(:experience_levels).where(experience_levels: {id: exp_ids})
Try this one. Note the lack of plural on the experience level.id
job_descriptions.includes(:experience_levels).where("experience_level.id = ?", exp_ids).references(:experience_levels)
I'm having a bit of difficulty figuring out how to do this in the "Rails" way, if it is even possible at all.
Background: I have a model Client, which has a has_many relationship called :users_and_managers, which is defined like so:
has_many :users_and_managers, -> do
Spree::User.joins(:roles).where( {spree_roles: {name: ["manager", "client"]}})
end, class_name: "Spree::User"
The model Users have a has_many relationship called credit_cards which is merely a simple has_many - belongs_to relationship (it is defined in the framework).
So in short, clients ---has many---> users ---has many---> credit_cards
The Goal: I would like to get all the credit cards created by users (as defined in the above relationship) that belong to this client.
The Problem: I thought I could achieve this using a has_many ... :through, which I defined like this:
has_many :credit_cards, through: :users_and_managers
Unfortunately, this generated an error in relation to the join with the roles table:
SQLite3::SQLException: no such column: spree_roles.name:
SELECT "spree_credit_cards".*
FROM "spree_credit_cards"
INNER JOIN "spree_users" ON "spree_credit_cards"."user_id" = "spree_users"."id"
WHERE "spree_users"."client_id" = 9 AND "spree_roles"."name" IN ('manager', 'client')
(Emphasis and formatting mine)
As you can see in the generated query, Rails seems to be ignoring the join(:roles) portion of the query I defined in the block of :users_and_managers, while still maintaining the where clause portion.
Current Solution: I can, of course, solve the problem by defining a plain 'ol method like so:
def credit_cards
Spree::CreditCard.where(user_id: self.users_and_managers.joins(:credit_cards))
end
But I feel there must be a more concise way of doing this, and I am rather confused about the source of the error message.
The Question: Does anyone know why the AR / Rails seems to be "selective" about which AR methods it will include in the query, and how can I get a collection of credit cards for all users and managers of this client using a has_many relationship, assuming it is possible at all?
The joins(:roles) is being ignored because that can't be appended to the ActiveRecord::Relation. You need to use direct AR methods in the block. Also, let's clean things up a bit:
class Spree::Role < ActiveRecord::Base
scope :clients_and_managers, -> { where(name: %w{client manager}) }
# a better scope name would be nice :)
end
class Client < ActiveRecord::Base
has_many :users,
class_name: "Spree::User",
foreign_key: :client_id
has_many :clients_and_managers_roles,
-> { merge(Spree::Role.clients_and_managers) },
through: :users,
source: :roles
has_many :clients_and_managers_credit_cards,
-> { joins(:clients_and_managers_roles) },
through: :users,
source: :credit_cards
end
With that setup, you should be able to do the following:
client = # find client according to your criteria
credit_card_ids = Client.
clients_and_managers_credit_cards.
where(clients: {id: client.id}).
pluck("DISTINCT spree_credit_cards.id")
credit_cards = Spree::CreditCard.where(id: credit_card_ids)
As you can see, that'll query the database twice. For querying it once, check out the following:
class Spree::CreditCard < ActiveRecord::Base
belongs_to :user # with Spree::User conditions, if necessary
end
credit_cards = Spree::CreditCard.
where(spree_users: {id: client.id}).
joins(user: :roles).
merge(Spree::Role.clients_and_managers)
I have two models - "Part" belongs_to "Category"
In this scenario is it possible to take this query:
Part.joins(:category).where("categories.name = 'cars'").first
And create a hashed variation of it? I've seen stuff like this:
Part.where(categories: {name: 'cars'})
However the SQL it generates for the two are pretty different. If (big if) I'm not doing anything wrong here, then why does the hash'd version not work in this case? And what are the relationships in which it is intended to work in?
In case it helps here are my models:
Category Model:
class Category < ActiveRecord::Base
has_many :parts
end
Parts Model:
class Part < ActiveRecord::Base
belongs_to :category
end
You've missed join() and first() methods in the second example. Use:
Part.joins(:category).where(categories: { name: 'cars' }).first
I have 3 models:
class ProductLine < ActiveRecord::Base
has_many :specifications
has_many :specification_categories, :through => :specifications,
end
class Specification < ActiveRecord::Base
belongs_to :product_line
belongs_to :specification_category
end
class SpecificationCategory < ActiveRecord::Base
has_many :specifications
has_many :product_lines, :through => :specifications
end
Basically, we are showing the specifications as a subset of data on the product line page and we would like to do something like (example only, yes I'm aware of N+1):
Controller:
#product_line = ProductLine.find(params[:id])
#specification_categories = #product_line.specification_categories)
View:
#specification_categories.each do |specification_category|
...
specification_category.specifications.each do |specification|
...
end
end
The issue here is getting rails to filter the specifications by ProductLine. I've tried constructing queries to do this but it always generates a separate NEW query when the final association is called. Even though we aren't using the code above now (not a good idea here, since we could potentially run into an N+1 problem), I'd like to know if it's even possible to do a 3 way join with association filtering. Has anyone run across this scenario? Can you please provide an example of how I would accomplish this here?
Prevent the N+1 by altering your line to:
#specification_categories = #product_line.specification_categories).include(:specifications)
OR
Construct your own query using .joins(:association), and do the grouping yourself.
sorry for the beginner's question, but I find it hard to understand:
I have assets, in the model:
class Asset < ActiveRecord::Base
belongs_to :project
belongs_to :image
belongs_to :text
belongs_to :link
belongs_to :video
belongs_to :audio
def self.search(search)
if search
where('title LIKE ?', "%#{search}%")
else
scoped
end
end
end
each asset type has its own table and has_many :assets definition in their model. I'd like to search through the respective asset tables and get items with a specific title (then return them as list of assets). How would it formulate the query? Do I need to use searchlogic?
Is there a chance you describe a bit more what you are trying to achieve? You have an Asset table. It seems this is where you would put the "title" and return all rows where your LIKE search matches.
And instead of listing multiple belongs_to statements, you may want to make this a polymorphic table instead:
class Asset < ActiveRecord::Base
belongs_to :assetable, :polymorphic => true
:
:
end
And then in each of the other models...
class Project < ActiveRecord::Base
has_many :assets, :as => :assetable
end
In this way every asset would have a searchable record in Assets. Use the guides or watch Ryan Bates rails cast on polymorphic associations.
I look forward to further information.
## UPDATE ##
Inside your Asset model, create a class method (e.g. search) like this:
def self.search(params) # these are from your search form
self.where("title like ?", "%#{params[:search]}%"
end
Params[:search] is the params form your form, and the Asset model retrieves all the records which match. Your data set will have the assetable_id and assetable_type (which is the other model). You could display these as links or use this data to retrieve additional info for each model returned, etc.
The key here, as yo will read about and has been explained to me, is to push as much of this type of logic to the model (fat models) and keep your controllers thin.
Hope this helps!