Limiting down the amount of calls to the database during a simple_form with multiple HABTM - ruby-on-rails

I am currently trying to speed up an old (3.2) Rails app and hitting an issue with way too many calls being made to the database during an edit view using simple_form
Here is the simplified setup;
class Event
HABTM Speakers
HABTM Sponsors
class Speaker
belongs_to Sponsor
HABTM Events
class Sponsor
have_many Speakers
HABTM Events
For an event we want to show both each potential speaker (of which there are thousands) and all the sponsors (hundreds).
For each speaker if they have a sponsor they need to show the sponsor name.
This all adds up to a lot of calls to the sponsors table, way too many.
Current Code
Right now the simple_form is set up as;
<%= simple_form_for [:admin, #event] do |f| %>
...
<%= f.association :speakers, :input_html => { :class => 'select2able' }, :label_method => :summary %>
...
<%= f.association :sponsors, :input_html => { :class => 'select2able' } %>
...
That label method is on the speaker model;
delegate :name, to: :sponsor, prefix: true, allow_nil: true
def summary
[name, job_title, sponsor_name].compact.join ' - '
end
The #event variable is assigned in the controller with a simple
#event = Event.find params['id']
This is where I have been spending most of my time trying to improve.
What I have tried
Calling Event.includes(:speakers => :sponsor).find() I thought would help. but it hasn't at all.
I have also tried just including speaker and sponsor separately.
I have searched around for people having issues with simple_form specifically but haven't found anything. I know this is a sign of the mental structure that was made, but anything I can do to improve it without a complete re-write would be excellent.

I was almost there with my original attempts.
This version of Rails didn't like the external includes (it executed but didn't apply anything)
The final code was a slight change where I brought the include inside the find_by.
Event.find_by_slug(params[:id], :include => [:speakers, :sponsors, :speakers => :sponsor])

Related

How do I call the name of a user when they belong to a model

I have two models, Chasing and User, a chasing belongs_to :user and a user has_many :chasings.
I created a migration for linking the two models together:
class AddUsersToChasings < ActiveRecord::Migration
def change
add_reference :chasings, :user, index: true, foreign_key: true
end
end
I have a controller for creating new users which I then want to be able to assign to chasings. I currently have this code in my chasings form for selecting the user:
<%= f.select :user_id, options_for_select(User.all.map {|c| [c.name, c.id]}), { :include_blank => "Please select user"}, {:class => "form-control"} %>
This seems to do the trick, after calling Chasing.first in rails console I can see the chasing now has user_id relevant to the user I picked. I can also run Chasing.first.user.name to give me the name of the user who is associated with the chasing. I'm wanting to show this name in my index view, the code I currently have for this is:
ChasingsController:
def index
#chasing = Chasing.all
end
Index view:
<% #chasing.each do |chasing| %>
<%= chasing.user %>
<% end %>
This shows a random string (seems to change every time I update a chasing - #<User:0xf5b0ba8> for example). when I change this to chasing.user.name I get 'undefined method `name' for nil:NilClass'.
Is there a way I can call the name for my view?
EDIT:
As per NickM's comment below I had chasings without users assigned to them causing active record to throw the error.
Looks like you have some Chasing objects in your database without user_ids. You can test by doing <%= chasing.user.name if chasing.user %>

Money Rails Gem - null values

I have monetised two models of my Rails 4 app with Money-Rails gem.
One is called Participants, the other is called Funding. Each of these models is nested inside another model, called Scope. Scope belongs to Project.
The associations are:
Project has one Scope; Scope belongs to Project
Scope has one Participant and has one funding; each of Participant and Funding belong to Scope.
Project accepts nested attributes for Scope. Scope accepts nested attributes for Participant and Funding.
Params for each relevant attribute in Participant and Funding are permitted in the Scope and Project Controllers as well as the models themselves. Params for Scope are permitted in the Scope and Project controllers.
In my Project form, I ask several questions. That form also has nested forms for each of the models which belong to it. Inside the Scope form, I ask users two boolean questions, being: Do you want participants? Do you want funding? Each of these models has a follow up question about participation cost and funding (those attributes are monetised).
If the answer to those questions is true, then I reveal the participant or funding form partial and ask how much money they want.
I have two problems:
First problem: Not null violation
1. If a user says they do want participants, but there is no associated costs, so that the boolean question inside the participant model asking whether there is cost involved with participation, I get an error that says:
ERROR: null value in column "participation_cost_pennies" violates not-null constraint
If a user says they don't want participants in answer to the question asked in the Scope form, I get the same error as in 1 above
Second problem: If I save an amount in the monetised fields, and come back to edit the project form, the form does not show the saved amount in the monetised field - and if you don't reenter it, I get an error saying that it can't be blank.
Does anyone know how to:
make the first problem go away in all circumstances except those when participation costs are actually sought; and
Fix the second problem by displaying the original amount saved when you come back to edit the form? I have tried inserting :selected into my form element, but it doesn't do anything.
My code is as follows:
Inside my Scope form (nested inside my project form):
<%= f.simple_fields_for :scope do |s_all| %>
<%= s_all.input :if_participant, :as => :boolean, :label => false, inline_label: 'Public participants or volunteers' %>
<%= s_all.input :if_funding, :as => :boolean, :label => false, inline_label: 'Funding or expenses' %>
If the answer to these fields is true, then I reveal the partial forms for participant of funding (for whichever is true).
Inside my Participants partial form, I have:
<%= f.simple_fields_for :scope do |participants_s| %>
<%= participants_s.simple_fields_for :participant do |par| %>
<%= f.label 'Are participants reimbursed for their costs?', :class => 'question-project' %>
<%= par.collection_radio_buttons :costs, [[true, ' Yes'], [false, ' No']], :first, :last, {:item_wrapper_class => 'fixradio'}, {:class => "response-project"} %>
<%= f.label 'What amount will you pay for participation costs?', :class => 'question-project' %>
<%= par.select :participation_cost_currency,
options_for_select(major_currencies(Money::Currency.table)), selected: :participation_cost_currency,
label: false,
prompt: "Select your costs currency" %>
<%= par.input :participation_cost, label: false, placeholder: 'Whole numbers only', selected: :participation_cost_pennies, :input_html => {:style => 'width: 250px; margin-top: 20px', class: 'response-project'} %>
For the first problem, you'll want to set a default value for the participation_cost_cents column in a migration:
# in console
> rails g migration change_default_for_participation_cost_cents
# in migration file
class ChangeDefaultForParticipationCostCents < ActiveRecord::Migration
def change
change_column :participants, :participation_cost_cents, :integer, default: 0
end
end
I'm not sure I follow on the second problem though. Maybe you should split the question in two?
A meetup group for Rails has helped me answer this question. The answer is not obvious - especially for newcomers.
My problem was I had an attribute in my database called participation_cost. Monetise then tried to make a method with the same name and that was failing because of the attribute in my table. For others, you don't need the attribute in your database with the name of the field you want to monetise.
Removing that attribute (in my case, participation_cost) solved my problem.

Ruby on Rails: Nested Model Form Checkbox For A 'has_many :through' Relationship

Still fairly new to RoR, let alone Ruby itself.
Here's my issue. I'm using Rails 4 and Ruby 2.1.0
I have a User class with this relationship:
has_many :roles, ->{ uniq }, :through => :user_roles
accepts_nested_attributes_for :roles
Now, the table 'roles' simply has a list of roles, such as 'admin', 'moderator', 'visitor', 'artist', etc.
The table 'user_roles', is just a relational table, with the role's key and the user's key.
So, to clarify, 'users' has a one to many relationship with 'user_roles' and "user_roles' table has a many to one relationship with the 'roles' table.
Something like this:
users: id, name, email #ie: 12, 'mikey', 'mike#foobar.com'
user_roles: id, user_id, role_id #ie: 4324, 12, 8
roles: id, name, weight #ie: 8, 'moderator', 11
I've been given the task of adding a checkbox toggle in the admin section so that an admin can grant the moderator role to any one user.
Each admin page is for one specific user. Therefore, the form_for is based on the #user instance, depending on which user the admin selected for editing.
RailsCasts #196 kind of helped me understand the concept, but it's platform is just different enough that I'm not sure how to exactly translate it into what I need. Likewise, there are some similar questions here on StackOverflow as well - but again, I can't seem to get it to translate correctly into my situation.
What I do have, I think should be at least somewhere in the realm of the right direction. THen again, maybe I'm way off. Here is my form code:
= form_for #user, :url => admin_user_path(#user), html:{class: 'form_horizontal'} do |f|
%br
%h4{class: :dc_green}Logistical Information
= f.label :display_name, "Display Name"
= f.text_field :display_name
= f.label :email, "eMail"
= f.text_field :email
%br
= f.fields_for :roles do |a|
= a.check_box :moderator, { inline_label: :none }
= a.label :moderator, {for: :moderator, class: "checkbox inline"}
%br
The error I'm getting with this form code is: undefined method `moderator'
Thank you in advance for anyone who can offer support :)
I guess I should probably add that I'm also clueless as to how I should handle this in the controller once it is submitted. But, first things first.
UPDATE
So, the reason I'm getting the undefined error is because it doesn't exist for that user. Which is bad. I need to be able to add the role to the user or remove the role. However, the only roles coming through are the roles the user has already been assigned to. So I'm totally doing this wrong. Now I'm really in the dark.

Rails:how to show name instead of ID or Address in has_many relation in active_admin?

These days I'm using the active_admin to manage my data. I have a Audio model and Problem model. Audio has many problems and Problem belongs to audio.
I use the active_admin to create the problems. But in the problem's new page, there is a drop-down list shows the content like:
#<Audio:0xb4116084>
With the address I can hardly recognize which file I want. What I want to show in the Audio's drop-down list is the Audio's title which is a column of Audio model. I just want to change this column in the new page, and others remain the same as default. What should I do? Thanks!
Audio class must implement display_name method
Ex
class Audio
def display_name
title
end
end
this is from active admin sources
# Active Admin makes educated guesses when displaying objects, this is
# the list of methods it tries calling in order
setting :display_name_methods, [ :display_name,
:full_name,
:name,
:username,
:login,
:title,
:email,
:to_s ]
Looks like you haven't such methods so to_s is called for Audio objects
You can use a :member_label.
Here is a example
form :html => { :enctype => "multipart/form-data" } do |f|
f.input :problems,
:input_html => { :multiple => false, :style => "width: 700px;"},
:collection => Audio.all,
:member_label => :audio_name
end

an example of a nested form in simple form?

I am still struggling both writing the controller and the actual form to be able to nest one form in another with an optional model?
I have Message which has many contacts
When submitting a message, I want to add a contact optionally.
I have this as an example:
= simple_form_for Message.new, :remote => true do |f|
#message_form
= f.error_messages
%p
= f.input :account_name, :url => autocomplete_account_name_messages_path, :size => 40, :as => :autocomplete
%p
= f.input :topic, :required => true,
:input_html => {:size => 30}
#add_contact_btn
= link_to "Add Contact"
#contact_form
= f.simple_fields_for :contactd do |fc|
= fc.input :email
= fc.input :first_name
= fc.input :last_name
= f.submit 'Give'
= f.submit 'Request'
For Message.rb model, I have the following:
has_many :contacts
accepts_nested_attributes_for :contacts, :reject_if =>:all_blank
Note: When I used :contacts in the simple_fields_for it didn't work, so it is singular. But the reverse for accepts_nested_attributess_for.
In my create controller for message, I included message.contacts.build
But right now I am still generating nil contacts.
Here is what I see passed as form data from google chrome:
message%5Baccount_name%5D:McKesson
message%5Btopic%5D:testing a contact
message%5Bbody%5D:testing this
sender_id:
receiver_id:23
message%5Bcontacts%5D%5Bemail%5D:888#gmail.com
message%5Bcontacts%5D%5Bfirst_name%5D:Ang
message%5Bcontacts%5D%5Blast_name%5D:Name
The correct method name is simple_fields_for (notice the plural)
Also, you need to keep the f. to call it on the simple_form object
I have a small project where I demonstrate how to use nested forms in simple-form, combined with cocoon (a gem I created to add/remove nested elements dynamically).
The project is on github.
Hope this helps.
In my create controller for message, I included message.contacts.build
But right now I am still generating nil contacts.
Make sure you put in your Message.rb model the ability for it to accept the attributes too.
class Message < ActiveRecord::Base
attr_accessible :contacts_attributes
has_many :contacts
accepts_nested_attributes_for :contacts
I know it doesn't answer your question fully but it may have just been this. When it comes to my project, it would return nil if i didn't include the :contacts_attributes, in my case it deals with products. Hope this helps even if I'm not using simple form as of now!
I faced similar issues working with nested forms. As suggested by JustinRoR you need to define
attr_accessible: contacts_attributes.
You should be able to test the hash in the ruby console ( I am not sure if you have tried this). I suggest you to print the params[:message] and use this to create message from console like Message.new(params[:message]). (Note params[:message] is what you get by printing the params[:message] hash).
Once it works in console it should work like charm

Resources