I am currently somewhat stuck figuring out an elegant solution to my following
problem:
Let's say I have the following classes:
class Event < ActiveRecord::Base
belongs_to :reg_template, :class_name => "EmailTemplate"
[...]
end
class EmailTemplate < ActiveRecord::Base
has_many :events
[...]
end
And a view that contains:
<%= f.collection_select(:reg_template_id, EmailTemplate.all, :id, :name) %>
What is the recommended way of processing this form field in an action
controller?
Having a 1:1 relationship between Event and EmailTemplate means that Rails
does not generate a reg_template_id and reg_template_id= method (as it would
do for a 1:n relationship), so attempts to read or assign this field will fail
with
unknown attribute: reg_template_id
when attempting to call
Event.update_attributes
Using
<%= f.collection_select(:reg_template, EmailTemplate.all, :id, :name) %>
instead also does not help much as it will fail with:
EmailTemplate(#70070455907700) expected, got String(#70070510199800)
I guess I must be missing something terribly obvious as I think is is rather
common to update a model instance with a reference to another object through a
collection_select.
If you have the column reg_template_id in the events table, then the following code should work:
<%= f.collection_select(:reg_template_id, EmailTemplate.all, :id, :name) %>
Related
I have a polymorphic association (belongs_to :resource, polymorphic: true) where resource can be a variety of different models. To simplify the question assume it can be either a Order or a Customer.
If it is a Order I'd like to preload the order, and preload the Address. If it is a customer I'd like to preload the Customer and preload the Location.
The code using these associations does something like:
<%- #issues.each do |issue| -%>
<%- case issue.resource -%>
<%- when Customer -%>
<%= issue.resource.name %> <%= issue.resource.location.name %>
<%- when Order -%>
<%= issue.resource.number %> <%= issue.resource.address.details %>
<%- end -%>
Currently my preload uses:
#issues.preload(:resource)
However I still see n-plus-one issues for loading the conditional associations:
SELECT "addresses".* WHERE "addresses"."order_id" = ...
SELECT "locations".* WHERE "locations"."customer_id" = ...
...
What's a good way to fix this? Is it possible to manually preload an association?
You can do that with the help of ActiveRecord::Associations::Preloader class. Here is the code:
#issues = Issue.all # Or whatever query
ActiveRecord::Associations::Preloader.new.preload(#issues.select { |i| i.resource_type == "Order" }, { resource: :address })
ActiveRecord::Associations::Preloader.new.preload(#issues.select { |i| i.resource_type == "Customer" }, { resource: :location })
You can use different approach when filtering the collection. For example, in my project I am using group_by
groups = sale_items.group_by(&:item_type)
groups.each do |type, items|
conditions = case type
when "Product" then :item
when "Service" then { item: { service: [:group] } }
end
ActiveRecord::Associations::Preloader.new.preload(items, conditions)
You can easily wrap this code in some helper class and use it in different parts of your app.
This is now working in Rails v6.0.0.rc1: https://github.com/rails/rails/pull/32655
You can do .includes(resource: [:address, :location])
You can break out your polymorphic association into individual associations. I have followed this and been extremely pleased at how it has simplified my applications.
class Issue
belongs_to :order
belongs_to :customer
# You should validate that one and only one of order and customer is present.
def resource
order || customer
end
end
Issue.preload(order: :address, customer: :location)
I have actually written a gem which wraps up this pattern so that the syntax becomes
class Issue
has_owner :order, :customer, as: :resource
end
and sets up the associations and validations appropriately. Unfortunately that implementation is not open or public. However, it is not difficult to do yourself.
You need to define associations in models like this:
class Issue < ActiveRecord::Base
belongs_to :resource, polymorphic: true
belongs_to :order, -> { includes(:issues).where(issues: { resource_type: 'Order' }) }, foreign_key: :resource_id
belongs_to :customer, -> { includes(:issues).where(issues: { resource_type: 'Customer' }) }, foreign_key: :resource_id
end
class Order < ActiveRecord::Base
belongs_to :address
has_many :issues, as: :resource
end
class Customer < ActiveRecord::Base
belongs_to :location
has_many :issues, as: :resource
end
Now you may do required preload:
Issue.includes(order: :address, customer: :location).all
In views you should use explicit relation name:
<%- #issues.each do |issue| -%>
<%- case issue.resource -%>
<%- when Customer -%>
<%= issue.customer.name %> <%= issue.customer.location.name %>
<%- when Order -%>
<%= issue.order.number %> <%= issue.order.address.details %>
<%- end -%>
That's all, no more n-plus-one queries.
I would like to share one of my query that i have used for conditional eager loading but not sure if this might help you, which i am not sure but its worth a try.
i have an address model, which is polymorphic to user and property.
So i just check the addressable_type manually and then call the appropriate query as shown below:-
after getting either user or property,i get the address to with eager loading required models
###record can be user or property instance
if #record.class.to_s == "Property"
Address.includes(:addressable=>[:dealers,:property_groups,:details]).where(:addressable_type=>"Property").joins(:property).where(properties:{:status=>"active"})
else if #record.class.to_s == "User"
Address.includes(:addressable=>[:pictures,:friends,:ratings,:interests]).where(:addressable_type=>"User").joins(:user).where(users:{is_guest:=>true})
end
The above query is a small snippet of actual query, but you can get an idea about how to use it for eager loading using joins because its a polymorphic table.
Hope it helps.
If you instantiate the associated object as the object in question, e.g. call it the variable #object or some such. Then the render should handle the determination of the correct view via the object's class. This is a Rails convention, i.e. rails' magic.
I personally hate it because it's so hard to debug the current scope of a bug without something like byebug or pry but I can attest that it does work, as we use it here at my employer to solve a similar problem.
Instead of faster via preloading, I think the speed issue is better solved through this method and rails caching.
I've come up with a viable solution for myself when I was stuck in this problem. What I followed was to iterate through each type of implementations and concatenate it into an array.
To start with it, we will first note down what attributes will be loaded for a particular type.
ATTRIBS = {
'Order' => [:address],
'Customer' => [:location]
}.freeze
AVAILABLE_TYPES = %w(Order Customer).freeze
The above lists out the associations to load eagerly for the available implementation types.
Now in our code, we will simply iterate through AVAILABLE_TYPES and then load the required associations.
issues = []
AVAILABLE_TYPES.each do |type|
issues += #issues.where(resource_type: type).includes(resource: ATTRIBS[type])
end
Through this, we have a managed way to preload the associations based on the type. If you've another type, just add it to the AVAILABLE_TYPES, and the attributes to ATTRIBS, and you'll be done.
So I am having some problems with strong parameters and nested forms (suprise! Nobody's ever had that problem before lol) and I've been looking at several threads, trying different solutions but still can't get a nested attribute to work so I turn to you, fellow programmers. Firstly lets look at the code, shall we? If you find anything fishy, let me know!
Aventure.rb
class Adventure < ActiveRecord::Base
belongs_to :user
has_many :photos
has_many :reservations
has_many :activity_dates
accepts_nested_attributes_for :activity_dates
...
end
Activity_date.rb
class ActivityDate < ActiveRecord::Base
belongs_to :adventure
has_many :time_slots
accepts_nested_attributes_for :time_slots
end
Timeslot.rb
class TimeSlot < ActiveRecord::Base
belongs_to :activity_date
end
_form.hmtl.erb
<%= form_for #adventure, html: {multipart: true} do |f| %>
...
<%= f.fields_for :activity_dates, #adventure.activity_dates.new do |a| %>
<%= a.date_field :date %>
<%= a.hidden_field :adventure_id, value: #adventure.id %>
<% end %>
adventure_controller.erb
def adventure_params
params.require(:adventure).permit(:activity_name,:description,:leader,
:company_name,:adress,:max_perticipants,
:price,:currency,{:activity_dates=>[:dates]})
end
Right, so when I inspect the params like below I get these
hashes (see img link):
render json: { p: params.inspect, ad:adventure_params.inspect }
I have concluded that activity_dates shows up in params, but NOT in ad:adventure_params. Using params[:activity_dates] gives a ForbiddenAttributesError, which was expected. That's not a good way of going about it, as it is not permitted in the strong params. I would however like to get :activity_dates with it's attribute date, and later on even it's nested attribute for :timeslots. But no matter how many solutions I have looked at, I have not been getting the desired results. What am I doing wrong? Help me Obi-Wan, you are my only hope!
For nested attributes you need to add '_attributes' to the end of field name when you are adding them to your strong parameters, so you need to permit activity_dates as activity_dates_attributes as follows:
params.require(:adventure).permit(:activity_dates_attributes=>[:dates])
or as follows with your other permitted parameters:
params.require(:adventure).permit(:activity_name,:description,:leader,
:company_name,:adress,:max_perticipants,
:price,:currency, :activity_dates_attributes=>[:dates])
For more information on whitelisting strong parameters here are some useful links here: http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html
https://github.com/rails/strong_parameters/
I have two objects: Wine, Brand
Brand has_many :wines
Wine belongs_to :brand
How can I simplify the following code:
<%= #wine.brand.name if #wine.brand %>
I realize it's already very simple, but I have some different complexities in my code that make this cumbersome. What I'd like to do is something along the lines of:
<%= &#wine.brand.name %>
Where it basically ignores the error. In PHP you can do this, I just can't find a corollary for ruby.
You can use try method:
<%= #wine.brand.try(:name) %>
I'd rather do this as follows:
class Wine
def brand_name
brand.present? ? brand.name : ''
end
end
This keeps your view slightly cleaner:
<%= #wine.brand_name %>
You can use delegate:
class Wine < ActiveRecord::Base
belongs_to :brand
delegate :name, to: :brand, prefix: true, allow_nil: true
end
This creates a Wine#brand_name method, returning either the brand's name or nil if brand does not exist.
I'm trying to display active projects per party in a drop down list. active_projects is a method within the Party model. The grouped_collection_select code below works however, when I attempt to convert my form into a simple_form, my active_projects method is no longer recognised.
Below are my two code extracts. The first working correctly while the other causes an error.
# rails default
<%= f.grouped_collection_select(:project_id,
Party.all,
:"active_projects(#{date.strftime("%Y%m%d")})",
:party_name,
:id, :project_name) %>
# simple form
<%= f.input :project_id,
collection: Party.all, as: :grouped_select,
group_method: :"active_projects(#{date})" %>
I know this one is a little old but I have a solution to this problem using simple_form. I am not sure if it is the best solution but it does work.
Basically, the issue comes down to passing in a value to the group_method. In my case I had a class that needed to get the current_users company that he/she belongs to. My model/database structure was like this:
Type -> Category
In my case the Type records were global and did not belong to a specific company. However, the category model records did belong to a specific company. The goal is to show a grouped select with global types and then company-specific categories underneath them. Here is what I did:
class Type < ActiveRecord::Base
has_many :categories
attr_accessor :company_id
# Basically returns all the 'type' records but before doing so sets the
# company_id attribute based on the value passed. This is possible because
# simple_form uses the same instance of the parent class to call the
# group_by method on.
def self.all_with_company(company_id)
Type.all.each do |item|
item.company_id = company_id
end
end
# Then for my group_by method I added a where clause that reuses the
# attribute set when I originally grabbed the records from the model.
def categories_for_company
self.categories.where(:company_id => self.company_id)
end
end
So the above is a definition of the type class. For reference here is my definition of the category class.
class Category < ActiveRecord::Base
belongs_to :company
belongs_to :type
end
Then on my simple_form control I did this:
<%= f.association :category, :label => 'Category', :as => :grouped_select, :collection => Type.all_with_company(company_id), :group_method => :categories_for_company, :label_method => :name %>
Basically instead of passing in the value we want to filter on in the :group_method property we pass it in on the :collection property. Even though it will not be used to get the parent collection it is just being stored for later use in the class instance. This way, when we call another method on that class it has the value we need to do our filtering on the child.
Ok so I have these 2 classes
class Interface < ActiveRecord::Base
belongs_to :hardware
end
and
class Hardware < ActiveRecord::Base
has_many :interfaces
end
I have a form for a predefined #hardware.interfaces which includes array of Interfaces which is handled like below
<%= text_field_tag "hardware[interfaces][][name]",interface.name %>
<%= text_field_tag "hardware[interfaces][][ip]",interface.ip %>
Now I try to do...
#hardware = Hardware.find(params[:id])
#hardware.update_attributes(params[:hardware])
and it throws the error
Interface(#37298420) expected, got HashWithIndifferentAccess(#24204840)
Could someone clue me in on what's going on? and how to solve this problem?
update_attributes updates the model attributes.. and you are trying to update another model attributes (the Interface class)
you want to use nested form & accepts_nested_attributes_for - you can see how in this guide