Add a custom validation in rails 3 - ruby-on-rails-3

I am working with a nested model a question has multiple answers and only one can be marked as correct how can i validate to check that only one question was marked as correct. The correct is a boolean field.
#question model
validate :one_correct_answers
def one_correct_answers
if self.choices.correct_choices > 1
errors.add(:base, "please select only one correct answer")
end
end

In Question model
class Question
has_many :choices
accepts_nested_attributes_for :choices, :reject_if => ->(choice){ choice[:value].blank? }
validate :only_one_correct_answer
private
def only_one_correct_answer
unless (choices.select{ |choice| choice.correct }.size == 1)
errors.add(:choices, "You must provide only 1 correct answer")
end
end
end
In Form HTML
<input name="question[choices_attributes][0][correct]" type="checkbox">
<input name="question[choices_attributes][1][correct]" type="checkbox">
<input name="question[choices_attributes][2][correct]" type="checkbox">
<input name="question[choices_attributes][3][correct]" type="checkbox"> ... till n
and in QuestionsController
#question = Question.new(params[:question])
#question.valid? => will automatically call Question#only_one_correct_answer and add errors,if any.
I hope, this will help you. :)

First, your checkboxes should look like this:
<input id="something_" name="something[]" type="checkbox" value="<%= some_id %>">
This way, when you submit the form, the params should look like and array of the checked checkboxes:
params: something => [1, 2]
Then, on your controller you set the variable correct_choices to this array, and validate with your custom validator.

Related

Search for multiple possible param values

At the moment I have a form in which the user can input price per person and/or duration and/or team_size. What I would like to accomplish is to retrieve all records from a table that match the user input and for this I set scope in the model:
scope :filter_by_team_size, -> (team_size) { where("team_size = ?", team_size) }
scope :filter_by_duration, -> (duration) { where("duration = ?", duration) }
scope :filter_by_price, -> (price) { where("price = ?", price) }
And then in the controller use that again to retrieve the records by doing so:
#experiences = policy_scope(Experience).order(team_size: :desc).geocoded.filter_by_team_size(params[:team_size]) if params[:team_size].present?
#experiences = policy_scope(Experience).order(duration: :desc).geocoded.filter_by_duration(params[:duration]) if params[:duration].present?
#experiences = policy_scope(Experience).order(price: :desc).geocoded.filter_by_price(params[:price]) if params[:price].present?
However, this only gives me only the records for which the first input value matches but ignores all other values. Additionally, when you are viewing the search results and use the filter again it should apply the filter only for the records it found already.
Any suggestion on how to solve this would be much appreciated!
One way to handle this is to use a virtual model that handles binding parameters to and from the form:
class SearchQuery
include ActiveModel::Model
include ActiveModel::Attributes
attribute :team_size, :integer
attribute :duration
attribute :price
end
You can then setup the form:
<%= form_with(model: (#search_query || SearchQuery.new), url: '/experiences', method: :get) %>
<div>
<%= f.label :team_size %>
<%= f.number_field :team_size %>
</div>
# ..
<% end %>
And then you can just bind the params to the model with ActionController::Parameters#permit just like you would with a normal ActiveRecord model:
class ExperiencesController
before_action :set_search_query, only: :index, if: ->{ params[:search_query].present? }
# ...
def index
#experiences = if #search_query
#search_query.build_scope(policy_scope(Experience))
else
policy_scope(Experience)
end.geocoded
end
private
def set_search_query
#search_query = SearchQuery.new(search_query_params)
end
def search_query_params
params.fetch(:search_query).permit(:team_size, :duration, :price)
end
end
This loopback will make the form stateful just like your normal CRUD forms. We have not actually implemented #build_scope yes so lets do so:
class SearchQuery
include ActiveModel::Model
include ActiveModel::Attributes
attribute :team_size, :integer
attribute :duration
attribute :price
def build_scope(base_scope)
compacted_attributes = attributes.reject { value.nil? || value.empty? }
compacted_attributes.each_with_object(base_scope) do |(k,v), base|
if base.respond_to? "filter_by_#{k}"
# lets you customize the logic with a scope
base.send("filter_by_#{k}", v) # the scope is responsible for ordering
else
# convention over configuration!
base.where(Hash[k,v]).order(Hash[k,:desc])
end
end
end
end
Since this uses convention over configuration you can get rid of those pointless scopes in your model.

Rails+simple_form_for Change common id

I want to change form common name in rendering side
_from.html.haml
..
= f.simple_fields_for Image.new do |form|
= render 'avatar_fields', f: form
..
_avatar_fields.html.haml
..
= f.hidden_field :imageable_type
..
This is rendering like
<input id="product_image_imageable_type" name="product[image][imageable_type]" type="hidden">
But i want to render like this
<input id="product_logo_attributes_imageable_type" name="product[logo_attributes][imageable_type]" type="hidden">
I don't want to edit my '_avatar_fields.html.haml' screen. Because it's common html.
Any suggestion please..?
you can do some thing like
...
= f.hidden_field :imageable_type, input_html: {id: 'product_logo_attributes_imageable_type'}
...
I hope that this helps you

Rails 3 - how to save (un)checked checkboxes?

I have in a form (form_tag) several checkboxes like this:
<%=check_box_tag 'model_name[column_name]', 1, (#data.model_name.column_name == 1 ? true : false)%>
And updating them like:
variable = ModelName.find(params[:id])
variable.update_attributes(params[:model_name])
This works only in a moment, when I check some checkboxes - send them and they will be saved. That's fine.
But when I uncheck all checkboxes - send form - so nothing happend, in the DB table will not set the value 0 in the columns...
Could you give me any tip, how to fix it?
Thank you in advance
This happens because an unchecked checkbox will not send any value to the server. To circumvent this Rails provides the check_box helper, which generates code like this:
<input type="hidden" name="model[attr]" value="0" />
<input type="checkbox" name="model[attr]" value="1" />
Alternatively, insert a hidden field with hidden_field_tag:
<%= hidden_field_tag 'model_name[column_name]', '0' %>
<%= check_box_tag 'model_name[column_name]', 1, (#data.model_name.column_name == 1 ? true : false) %>

How to populate a rails simple_form select box using an index?

hopefully this isn't too complicated..
So I have a model with LOTS of attributes, which I've decided to store as indexes in the database which refer to an constant at the model:
class Profile < ActiveRecord::Base
STATUS_CHOICES = %w( single relationship married divorced complicated open )
etc...
In my form, I'm doing this right now:
= f.simple_fields_for :profile do |p|
= dp.input :relationship_status, :required => true, :collection => Datingprofile::STATUS_CHOICES
This displays the collection very well, but of course, the value needs to be set from the index coming through from the model. How would I set the values on the collection to correspond to the correct index of the STATUS_CHOICES array?
Update: Going to re-architect this to make the enum-ed attribs actual AD objects
model
some sort of constant hash:
HASH_NAME = {
0 => "Choose:",
1 => "On-Campus Recruiting - CSO",·
2 => "CSO Staff Referral",
3 => "Faculty Contact",·
4 => "Career Day",·
5 => "CSO Summer Job Listing",·
6 => "Alumni Contact",·
7 => "Personal Contact",·
8 => "Other"·
}
-- view
<%= f.input :some_field, :collection => Model::HASH_NAME.sort.map {|k,v| [v,k]} %>
This would output nice select with select-value as hash key and select-name as hash value, such as:
<select id="form_application_job_source" class="select required" name="form_application[job_source]">
<option value="0">Choose:</option>
<option value="1">On-Campus Recruiting - CSO</option>
<option value="2">CSO Staff Referral</option>
<option value="3">Faculty Contact</option>
<option value="4">Career Day</option>
<option value="5">CSO Summer Job Listing</option>
<option value="6">Alumni Contact</option>
<option selected="selected" value="7">Personal Contact</option>
<option value="8">Other</option>
</select>
Now, if you select say 'On-Campus Recruiting - CSO' from the dropdown, the value that would be stored is : 1
To show it in the view as 'On-Campus Recruiting - CSO' you would have to create a small return function like so in the model itself :
def return_paper_type
HASH_NAME[id]
end
In the view, it would be like this : <%= #instancevariable.return_paper_type %>
One way to quickly solve this would be to make the collection the enumerated indices for the option element values, then use the STATUS_CHOICES array to get the labels using :label_method.
= f.simple_fields_for :profile do |p|
= dp.input :relationship_status, :required => true, :collection => 0..Datingprofile::STATUS_CHOICES.length, :label_method => lambda { |i| Datingprofile::STATUS_CHOICES[i] }
References:
https://github.com/plataformatec/simple_form#collections
https://github.com/plataformatec/simple_form/blob/master/test/inputs/collection_select_input_test.rb#L141
Decided to implement this constants of hashes instead of arrays, which affords the simplicity of one model without the complexity of dealing with indexes..I'll store the hash keys and use the values only in the view.
Whee!!
Add this line to your application's Gemfile:
gem 'enum_help'
And then execute:
$ bundle
In model
class Profile < ActiveRecord::Base
enum relationship_status:{single: 0,relationship:1, married:2, divorced:3, complicated:4, open:5}
etc...
In _form.html.erb using simple_form:
<%= f.input :relationship_status %>
For more information: https://github.com/zmbacker/enum_help

Send to multiple recipients in Rails with ActionMailer

I'm trying to send multiple emails based on a boolean value in my database. The app is a simple scheduling app and user can mark their shift as "replacement_needed" and this should send out emails to all the users who've requested to receive these emails. Trouble is, it only every seems to send to one email. Here's my current code:
def request_replacement(shift)
#shift = shift
#user = shift.user
#recipients = User.where(:replacement_emails => true).all
#url = root_url
#recipients.each do |r|
#name = r.fname
mail(:to => r.email,
:subject => "A replacement clerk has been requested")
end
end
In the Rails guides (Action Mailer Basics) it says the following regarding multiple emails:
The list of emails can be an array of email addresses or a single string with the addresses separated by commas.
So both "test1#gmail.com, test1#gmail.com" and ["test1#gmail.com", "test1#gmail.com"] should work.
See more at: http://guides.rubyonrails.org/action_mailer_basics.html
You can just send one email for multiple recipients like this.
def request_replacement(shift)
#shift = shift
#user = shift.user
#recipients = User.where(:replacement_emails => true)
#url = root_url
emails = #recipients.collect(&:email).join(",")
mail(:to => emails, :subject => "A replacement clerk has been requested")
end
This will take all your #recipients email addresses and join them with ,. I think you can also pass an array to the :to key but not sure.
The only problem is you won't be able to use #name in your template. :(
I have the same problem. I don't know what is the deal is. I sidestep it by:
instead of calling
Mailer.request_replacement(shift).deliver
from my controller,
I'd define a class method on the mailer, and call that. That method would then iterate through the list and call deliver "n" times. That seems to work:
class Mailer
def self.send_replacement_request(shift)
#recipients = ...
#recipients.each do |recipient|
request_replacement(recipient, shift).deliver
end
end
def request_replacement(recipient, shift)
...
mail(...)
end
end
and from the controller, call
Mailer.send_replacement_request(shift)
To prevent each recipient from seeing the other email addresses:
#recipients.each{ |recipient| Mailer.request_replacement(recipient, shift).deliver }
I'm using Rails 5 and I have the same situation, the email was sent only to the last recipient but also it was sent just as plain text not as HTML email.
After trying some advices, I ended up fixing it in this way:
The mailer:
class BrochureMailer < ApplicationMailer
default from: "info#site.com"
def newsletter(sponsor, brochures_list)
#sponsor = sponsor
#brochures = brochures_list
mail(
to: #sponsor.email,
subject: "Interesting subject!"
)
end
end
The controller where the mailer is invoked:
class Admin::DashboardController < Admin::BaseController
def send_newsletter
sponsors = params[:sponsor_ids]
brochures = params[:brochure_ids]
sponsors = Sponsor.where(id: sponsors)
brochures = Brochure.where(id: brochures).to_a
# Send Newsletter email to the given Sponsors
sponsors.each do |sponsor|
BrochureMailer.newsletter(sponsor, brochures).deliver_later
end
redirect_back(fallback_location: admin_root_path, success: 'Newsletter sent!')
end
end
And in the view, something like this:
<% #brochures.each do |brochure| %>
<table width="280" border="0" cellpadding="0" cellspacing="0" align="left" valign="top" class="floater">
<tr>
<td align="center" valign="top">
<a target="_blank" href="<%= brochure_url(brochure) %>">
<img border="0" vspace="0" hspace="0" src="<%= brochure.image.blank? ? 'default.png' : brochure.image.url(public: true) %>" width="250" height="142">
<b><%= brochure.title %></b>
</a>
<br>
<%= brochure.description.truncate(60) %>
</td>
</tr>
</table>
<% end %>
And it works like a charm!
I'm not sure if this is the correct way or the most optimal way to go but just consider it as a second possibility.
I hope it could be useful for somebody else.