Rails 4 Polymorphic associations and concerns - ruby-on-rails

I'm trying to add an Evaluation model to my Rails 4 app.
I have made a model called evaluation.rb. It has:
class Evaluation < ActiveRecord::Base
belongs_to :evaluator, :polymorphic => true
belongs_to :evaluatable, :polymorphic => true
I have also made concerns for evaluator and evaluatable as:
module Evaluator
extend ActiveSupport::Concern
included do
has_many :given_evaluations, as: :evaluator, dependent: :destroy, class_name: 'Evaluation'
end
end
module Evaluatable
extend ActiveSupport::Concern
included do
has_many :received_evaluations, as: :evaluatable, dependent: :destroy, class_name: 'Evaluation'
end
end
I have included each concern in my user model:
class User < ActiveRecord::Base
include Evaluator
include Evaluatable
In my show page, I want to show a particular user's evaluations (received from other users -who are evaluators).
In my show, I have:
<% Evaluation.find(params[:id]).evaluations.order('created_at DESC').each do |eval| %>
<div id="portfolioFiltering" class="masonry-wrapper row">
<%= eval.remark %>
<%= eval.personal_score %>
<small><%= eval.created_at %></small>
In my evaluations form, I"m not sure how to designate the recipient of the evaluation. I have made the basic form, but I'm not clear about how to tie it to the user who should receive the evaluation.
<%= simple_form_for(#evaluation) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :score, collection: 1..10, autofocus: true, :label => "How do you rate this experience (1 being did not meet expectations - 10 being met all expectations) ?" %>
<%= f.input :remark, as: :text, :label => "Evaluate your project experience", :input_html => {:rows => 10} %>
My evaluations table has:
t.integer "user_id"
t.integer "evaluatable_id"
t.string "evaluatable_type"
t.integer "overall_score"
t.integer "project_score"
t.integer "personal_score"
t.text "remark"
t.boolean "work_again?"
t.boolean "continue_project?"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "evaluations", ["evaluatable_type", "evaluatable_id"], name: "index_evaluations_on_evaluatable_type_and_evaluatable_id", unique: true, using: :btree
QUESTIONS
How do I setup the show page to show a user's evaluations received?
How do I adapt the form so that it specifies a user id as the person who should receive the evaluation?

How do I setup the show page to show a user's evaluations received?
Your model concerns should help you with that. In your UsersController#show action, simply adding the following should do the trick:
#received_evaluations = #user.received_evaluations
Then you can use it in your show template:
<% #received_evaluations.each do |evaluation| %>
// render some view stuff
<% end %>
Or use collection rendering.
note: the Evaluation.find(...) that's currently in your view should be put in the controller action, it's not good practice to leave that in the view.
How do I adapt the form so that it specifies a user id as the person who should receive the evaluation?
If you have identified the user that will serve as evaluatable you could set it in your controller action or in your view form in case you have a list of users to evaluate on your page.
In the controller:
#evaluation.evaluatable_id = user_to_evaluate.id
#evaluation.evaluatable_type = user_to_evaluate.class.to_s
Or this simpler statement should do the same:
#evaluation.evaluatable = user_to_evaluate
Similarly, you should be able to set the evaluator the same way:
#evaluation.evaluator = user_that_evaluates
In the view:
<% #users_to_evaluate.each do |user| %>
<%= simple_form_for(Evaluation.new) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :score, collection: 1..10, autofocus: true, :label => "How do you rate this experience (1 being did not meet expectations - 10 being met all expectations) ?" %>
<%= f.input :remark, as: :text, :label => "Evaluate your project experience", :input_html => {:rows => 10} %>
<%= f.hidden_field :evaluator_id, :value => current_user.id %>
<%= f.hidden_field :evaluator_type, :value => current_user.class.to_s %>
<%= f.hidden_field :evaluatable_id, :value => user.id %>
<%= f.hidden_field :evaluatable_type, :value => user.class.to_s %>
</div>
<% end %>
<% end %>

Related

Unpermitted parameter simple_form

I am trying to create a nested form with Simple_fields in ruby 4.
However, every time i try to enter data into the form I get a unpermitted parameter error in the server console after trying to submit.
I already tried the solutions found in the simple_form wiki and did some testing, but that doesn't seem to work.
The _form:
<%= simple_form_for(#enquiry) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<H1>Algemene informatie</H1>
<%= f.input :reference, placeholder: 'Referentie' %>
<br>
<%= f.label :Locatie %>
<%= f.select :location, [['Chemiepark', 'chemiepark'], ['Farmsum', 'farmsum'], ['Winschoten', 'winschoten']] %>
<br>
<%= f.input :description, placeholder: 'Omschrijving' %>
<br>
<%= f.input :date %>
<br>
<%= f.input :amount, placeholder: 'Aantal' %>
</div>
<hr>
<% if false %>
<div class="form-inputs">
<%= f.simple_fields_for :enquiry_measures do |e| %>
<H1>Maatregelen</H1>
<%= e.input :responsible, placeholder: 'Verantwoordelijke' %>
<br>
<%# e.input :needed, as: :check_boxes,
collection: ["ja", "nee"] %>
<% end %>
<br>
</div>
<% end %>
<div class="form-inputs">
<%= f.simple_fields_for :tools do |t| %>
<% #enquiry.tools.each do |tool| %>
<%= field_set_tag 'Tool' do %>
<%= f.simple_fields_for "tool_attributes[]", tool do |tf| %>
<h1>Gereedschappen</h1>
<br>
<%= tf.input :handtool, placeholder: 'Handgereedschap' %>
<% end %>
<% end %>
<% end %>
<% end %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
The strong attributes plus what i tested:
def enquiry_params
# was gegenereerd door de scaffold params.fetch(:enquiry, {})
params.require(:enquiry).permit(:reference, :location, :description, :date, :amount,
:enquiry_measures_attributes => [:done, :responsible, :needed], :tools_attributes => [:handtool] )
#:enquiry_measures_attributes => [:done, :responsible, :needed])
#enquiry_measure_attributes: [:done, :responsible, :needed] )
update
code from models
class Enquiry < ActiveRecord::Base
#ophalen van andere tabellen voor het formulier. Has_many is 1 op veel relatie
#accepts_nested_attributes Nested attributes allow you to save attributes on associated records through the paren
# de dere regel zorgt ervoor dat de maatregelen worden opgehaald via de tussentabel enquiry_measures.
has_many :enquiry_measures, :class_name => 'EnquiryMeasure' #, inverse_of: :Enquiry
accepts_nested_attributes_for :enquiry_measures, :allow_destroy => true
has_many :measures, -> { uniq }, :class_name => 'Measure', :through => :enquiry_measures, dependent: :destroy
accepts_nested_attributes_for :measures, :allow_destroy => false
has_many :controls, :class_name => 'Control' #, inverse_of: :Enquiry
has_many :applicants, :class_name => 'Applicant' #, inverse_of: :Enquiry
has_many :agrees, :class_name => 'Agree' #, inverse_of: :Enquiry
has_many :signatures, :class_name => 'Signature' #, inverse_of: :Enquiry
accepts_nested_attributes_for :signatures, :allow_destroy => false
has_many :tools, :class_name => 'Tool', :dependent => :destroy #, inverse_of: :Enquiry
accepts_nested_attributes_for :tools, :allow_destroy => true
#:dependent => :destroy zorgt ervoor dat de foreign record ook word verwijderd.
#de instances van andere tabellen:
e = Enquiry.new
e.enquiry_measures.build(:enquiry_id => :id)
e.measures.build
# 28-11 MG de pagina's die in het form worden gebruikt.
cattr_accessor :form_steps do
%w(basic when measurements tool)
end
attr_accessor :form_step
validates :reference, presence: true, if: -> { required_for_step?(:basic) }
validates :amount, :date, presence: true, if: -> { required_for_step?(:when) }
#validates :needed, presence: true, if: -> { required_for_step?(:measurements) }
def required_for_step?(step)
return true if form_step.nil?
return true if self.form_steps.index(step.to_s) <= self.form_steps.index(form_step)
end
#voor het mailen met behulp van de mailgem:
# Declare the e-mail headers. It accepts anything the mail method
# in ActionMailer accepts.
def headers
{
:subject => "My Contact Form",
:to => "marco.groenhof#jpbgroep.nl",
:from => %("#{name}" <#{email}>)
}
end
end
and 1 of the related models: in this case enquiry_measure
class EnquiryMeasure < ActiveRecord::Base
belongs_to :enquiry
validates_presence_of :enquiry
has_many :measure
#serialize zodat de data uit de collection select met multiple: true op kan worden geslagen.
serialize :measure
end
and tools:
class Tool < ActiveRecord::Base
belongs_to :enquiry, :class_name => 'Enquiry' #, inverse_of: :applicant
validates_presence_of :enquiry
end
I know class_name is not really needed anymore.
UPDATE
The logging:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"i3YukMoOaYEuUdxk6kmhoQ5q9uLQHHISW+NAU/L+kNjSwMZERmdIIVXZvJUh0vTnEPidaMvMEIlVT/aTlkTNPw==", "enquiry"=>{"reference"=>"Test", "location"=>"chemiepark", "description"=>"3ro0qjhrojeofj", "date(1i)"=>"2017", "date(2i)"=>"1", "date(3i)"=>"3", "amount"=>"2", "tools_attributes"=>{"0"=>{"handtool"=>"Hamer"}}}, "commit"=>"Create Enquiry"}
The only weird thing i see is the "tools_attributes"=>{"0"=>{"handtool"=>"Hamer"}}}
Why is that 0 there? Could it be the id, because that would make sense to why i can not save.
And just to make sure, this is the tool tabel and foreign key:
add_index "tools", ["enquiry_id"], name: "index_tools_on_enquiry_id", using: :btree
create_table "users", force: :cascade do |t|
t.string "name", limit: 255
t.string "email", limit: 255
t.string "password_digest", limit: 255
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "tools", "enquiries"
UPDATE 4/01
Just installed rails panel to see what that would say, but i keep thinking the problem is within the parameters:
{"reference":"test","location":"chemiepark","description":"iwopjf","date(1i)":"2017","date(2i)":"1","date(3i)":"4","amount":"2","tools_attributes":{"0":{"handtool":"hamer"}}}
Why does it keep sending that 0? i suspect it to be the tool id, which would declare the not being able to save.
Try making you strong parameter as
params.require(:enquiry).permit(:reference, :location, :description, :date, :amount, enquiry_measures_attributes: [:id, :done, :responsible, :needed, :_destroy], tools_attributes: [:id, :handtool, :_destroy] )
And your model that is being nested should be something as below. Try doing this once.
class Tool < ActiveRecord::Base
# For a while let's not have any validation.
end
Hope this will work in your case as I fix it for my own.
I decided to updatew this question as i got the answer that helped me. It may not be the completely correct answer, but i managed to solve my issue by using the default form_for in uby on Rails. A bit more coding work, but it does work.

Updating a Many-to-many relationship with additional field by form_for

Is there a way to update a field relative to a many-to-many associations via form_for?
Theses are my models:
class Grad < ActiveRecord::Base
belongs_to :school
has_many :grad_courses
has_many :courses, through: :grad_courses
accepts_nested_attributes_for :grad_courses, :courses
end
class Course < ActiveRecord::Base
belongs_to :school
has_many :grad_courses
has_many :grads, through: :grad_courses
end
class GradCourse < ActiveRecord::Base
belongs_to :grad
belongs_to :course
end
Here is the many-to-many migration with the extra property (semester):
create_table "grad_courses", force: :cascade do |t|
t.integer "grad_id"
t.integer "course_id"
t.integer "semester"
t.datetime "created_at"
t.datetime "updated_at"
end
What i'm trying to do is add and update the relations with the semester information dynamically with a form collection set (using form_for and chosen).
<%= form_for #grad, html: {class: "form-horizontal"} do |f| %>
...
<% (1..#grad.max).each do |semester| %>
<div class="form-group">
<%= f.label :course_ids, "#{semester}º semestre", class: "col-sm-2 control-label" %>
<div class="col-sm-10">
<%= f.collection_select :course_ids, #courses, :id, :name, {include_blank: true}, {multiple: true, :class=>'chosen-select form-control'} %>
</div>
</div>
<% end %>
...
<% end %>
This way, it is creating and updating the grad_courses, but without the semester.
I've tried lot of things, but didn't had success.
I'm able to manage the semester's info directly, like in this post: http://stackoverflow.com/questions/2168442/many-to-many-relationship-with-the-same-model-in-rails#=
I could pass an array with a hidden field, and manage the form manually, but it would take too much work, so I was wondering if there is an easy way to do this.
Something like :grad_course[:course_id][:semester] in :grad params? Is it possible?
the grad_course_controller:
def update
respond_to do |format|
if #grad.update(grad_params)
format.html { redirect_to #grad, notice: 'Cursos adicionados com sucesso' }
else
format.html { render :edit }
end
end
end
private
def set_grad
#grad = Grad.find(params[:id])
end
def set_courses
#courses = Course.where(school_id: #grad.school_id)
#gc = GradCourse.where(grad_id: #grad.id)
end
list through.
def grad_params
params.require(:grad).permit(:name, :min, :max, :school_id, course_ids: [] )
end
Update
I've been trying to solve this using Peter's advices, so I tried nested_attributes and fields_for. But I'm having problems with it too.
I need one collection_sellect for each semester (from 1 to Grad.max number), where the options will be the courses available for that semester (#courses), and for the selected ones a grad_course relation needs to be created or updated.
The problem with field_for is that one field is generated for every grad_course relation, and since each field is related with the grad_course.id I`m not able to add new ones properly.
Here is one of the tries i've made with fields for:
<%= f.fields_for :grad_courses do |builder| %>
<p>
<%= builder.text_field :semester %><br />
<%= builder.collection_select :course_id, #courses, :id, :name, {include_blank: true}, {multiple: true, :class=>'chosen-select form-control'} %>
</p>
<% end %>
with these changes in the grad_controller:
def grad_params
params.require(:grad).permit(:name, :min, :max, :school_id, course_ids: [], grad_courses_attributes: [:id, :course_id, :grad_id, :semester ])
end
Have you tried to add the :course_ids on the permit list?
Like this:
def grad_params
params.require(:grad).permit(:name, :min, :max, :school_id, :course_ids)
end

Active record "date" and "nationality" function giving me error. (Undefined method)

I am trying to create a profile page where I can input "born_on". It will be using the
class CreateWineMakers < ActiveRecord::Migration
def change
create_table :wine_makers do |t|
t.string :name
t.date :born_on
t.text :nationality
t.text :profile
t.text :wine
t.integer :wine_list_id
t.timestamps
end
add_index :wine_makers, :wine_list_id
end
end
Here is my view file.
<%= simple_form_for WineMaker.new do |f| %>
<%= f.input :name %>
<%= f.input :profile %>
<%= f.input :wine %>
<%= f.input :born_on %>
<br/>
<%= f.submit "Create", :class => 'btn btn-primary' %>
<% end %>
The "born_on" is giving me error saying the method is not defined. I am confused since all other inputs are working except "born_on" and "nationality". Before, my "born_on" was named "birth_date", and I thought the naming convention was wrong and changed it to "born_on". Here is the controller.
class WineMakersController < ApplicationController
def new
#wine_maker = WineMaker.new
end
def create
#wine_maker = WineMaker.create(wine_maker_params)
redirect_to wine_list_path(#wine_list)
end
def show
end
private
def wine_maker_params
params.require(:wine_maker).permit(:name, :born_on, :nationality, :profile, :wine )
end
end
This seems like such an easy question that I couldn't find similar problems..
Thank you.

Setting a primary attribute via form radio buttons

So I'm having an issue setting a primary image for my Dress object via a form.
The form allows the user to edit the dress details and then add/remove images to the form (using nested_form) and for each of them set a label and assign a primary image.
Everything works so far except for setting the primary image via radio buttons.
Dress Model:
class Dress < ActiveRecord::Base
has_many :dress_images
has_one :primary_dress_image, :class_name => "DressImage", :conditions => { :is_primary => true }
accepts_nested_attributes_for :dress_images, :allow_destroy => true
validates :name, :presence => true, :length => { :maximum => 99 }
end
DressImage Model
class DressImage < ActiveRecord::Base
belongs_to :dress
# Same as:
# def self.primary
# where(:is_primary => true)
# end
scope :primary, where(:is_primary => true)
# clear old primary if:
# this is a new record
# this is existing and is_primary has been set to true
before_save :clear_primary,
:if => Proc.new{ |r| (r.new_record? && r.is_primary) || (r.is_primary_changed? && r.is_primary) }
validates :label, :presence => true, :length => { :maximum => 60 }
validates :caption, :length => { :maximum => 200 }
mount_uploader :image, ImageUploader
def clear_primary
DressImage.update_all( {:is_primary => false}, :dress_id => self.dress_id )
end
end
Dress edit form
<h1>Dress</h1>
<% #dress.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %> <%#= f.label :name %>
<%= nested_form_for #dress, :as => :dress, :url => { :action => :update }, :html=>{ :multipart => true } do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :description %>
<%= f.text_area :description %>
<%= f.fields_for :dress_images do |dress_image_form| %>
<div class="dress-image">
<%= image_tag dress_image_form.object.image_url(:thumb) %>
<%= dress_image_form.text_field :label %>
<%= dress_image_form.file_field :image %>
<div class="primary-image-radio">
<%= dress_image_form.label :is_primary, "Main Image" %>
<%= f.radio_button :primary_dress_image_id, dress_image_form.object.id %>
</div>
<p>
<%= dress_image_form.link_to_remove "Remove this attachment" %>
</p>
</div>
<% end %>
<%= f.link_to_add "Add Photo", :dress_images %>
<%= f.submit "Save Dress" %>
<% end %>
With this radio button, the primary_dress_image_id attribute is set on the Dress object, but #dress.primary_dress_image gives a different result to the ID.
If I change the radio button to <%= dress_image_form.radio_button :is_primary, true %> it works better but because the name of each radio button is different, they are not treated as the same group.
I'm new to rails so I might be missing something completely obvious or doing it all wrong.
Here's one solution.
Add a hidden input to each of your nested field groups. Use a radio_button_tag instead of radio_button to make sure they are in the same group:
<div class="dress-image">
<%= dress_image_form.hidden_field :is_primary, :class => 'primary-image' %>
...
<%= radio_button_tag "select_primary_image", true, dress_image_form.object.is_primary? %>
...
</div>
Then add some javascript to update the hidden field according to the radio button selection:
$("body").on "change", ".primary-image-radio input:radio" ->
$(#).closest(".dress-image").find(".primary-image").val( $(#).is(":checked") )
You might need to modify the code a little because it's just an untested quick example, but it should give you an idea.
So I ended up using this method: <%= dress_image_form.radio_button :is_primary, true %> and using jquery to deselect all the other radio buttons when one is clicked.
This seems like a bit of a hacky method to me - there must be a purely Rails way of doing this without having to resort to JS? Until I find a better one, I'm going to stick with this solution.

Rails - Liking Comments Made on Posts - NoMethodError

For my application, I have Users, who can Create Project Postings. On each Project Posting, they can make comments that I have made a Blogupdate model. I want users to be able to like Blogupdates made on each Project page.
So, I created a Bloglike model. But when I try to render a LIKE/UNLIKE button, I get the following error:
NoMethodError in Projects#blogs
undefined method `bloglikes_path'
Extracted source (around line #11):
11: <%= form_for(current_user.bloglikes.build(blogupdate_id: blogupdate.id)) do |f| %>
Question: As a note, I have not built up the controller for the actual create/destroy function in my bloglikes controller; but looking at my attached code below, does somebody know how I can resolve this error so the like/unfollow button renders?
schema.rb
create_table "bloglikes", :force => true do |t|
t.integer "user_id"
t.integer "blogupdate_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "bloglikes", ["blogupdate_id"], :name => "index_bloglikes_on_blogupdate_id"
add_index "bloglikes", ["user_id", "blogupdate_id"], :name => "index_bloglikes_on_user_id_and_blogupdate_id", :unique => true
add_index "bloglikes", ["user_id"], :name => "index_bloglikes_on_user_id"
user.rb
has_many :bloglikes, foreign_key: "user_id"
has_many :liked_blogupdates, through: :bloglikes, source: :blogupdate
blogupdate.rb
has_many :bloglikes, foreign_key: "blogupdate_id"
has_many :liked_by, through: :bloglikes, source: :user
def liking_blogupdate?(blogupdate)
bloglikes.find_by_blogupdate_id(blogupdate.id)
end
def like_blogupdate!(blogupdate)
bloglikes.create!(blogupdate_id: blogupdate.id)
end
def blogupdate_unlike!(blogupdate)
bloglikes.find_by_blogupdate_id(blogupdate.id).destroy
end
bloglike.rb
class Bloglike < ActiveRecord::Base
attr_accessible :blogupdate_id
belongs_to :user, foreign_key: "user_id"
belongs_to :blogupdate, foreign_key: "blogupdate_id"
end
projects_controller.rb
def blogs
#project = Project.find(params[:id])
#blogupdates = #project.blogupdates.newest.page(params[:blogupdates_page]).per_page(5)
end
views/projects/blogs.html.erb
<%= render 'blogs' %>
views/projects/_blogs.html.erb
<%= render #blogupdates %>
views/blogupdates/_blogupdates.html.erb
<%= blogupdate.liked_by.count %>
<% if current_user.liking_blogupdate?(blogupdate) %>
<%= form_for(current_user.bloglikes.find_by_blogupdate_id(blogupdate),
html: { method: :delete }) do |f| %>
<%= f.submit "UNLIKE", class: "btn btn-medium" %>
<% end %>
<% else %>
<%= form_for(current_user.bloglikes.build(blogupdate_id: blogupdate.id)) do |f| %>
<div><%= f.hidden_field :blogupdate_id %></div>
<%= f.submit "LIKE", class: "btn btn-medium btn-primary" %>
<% end %>
<% end %>
<p><%= raw blogupdate.content %></p>
UPDATE: As noted below by #Dan, I forgot to update the routes.rb file. I added "resources :bloglikes" and it worked now.
You didn't post your routes.rb file but I'd wager that is where the problem is at. An undefined method related to routes (e.g. bloglikes_path) typically indicates you've not defined the routes.
Add resources :bloglikes to your project's routes.rb file and see if that resolves the issue.

Resources