Validating fields that aren't in the model/controller in Rails - ruby-on-rails

I'm a relative newbie at Rails, so forgive me if my question's got an obvious answer.
I'm trying to include a field in a Rails form which isn't in the model/controller or the migration associated with the view.
The form is a simple public contact form, and I can validate against most of the fields easily enough. Eg name, email etc.
The model is form_submission.rb
However, I have a field in the contact form - captcha - which isn't mirrored in the form_submissions db table, etc.
There is a separate table, model etc for captcha which is captcha_answer.rb (etc)
The attributes for captcha_answer in the migration are: answer and is_correct.
The table simple contains a list of answers to a predefined question, some of which are true and some which are false.
Eg, the captcha question might be:
Which is these is an animal?
With the options of: cat, dog, tree, rabbit .. in a select.
What I want to be able to do is to validate that:
a) The captcha field exists in the POST (return message of "no captcha given" if not)
b) The answer given has a value in captcha_answers.is_correct of true (return message of "you gave a wrong answer" if not)
The capcha_answers.answer is always unique, so I want to do the equivalent of a SQL query which gets the first record where captcha_answers.answer = and returns the value of captcha_answers.is_correct
Like I say, if the attribute was in form_submissions then I'd be able to validate it no problem, but I can't figure out how I can validate a field against something in another model.
Any ideas?

You can just add for example a hidden field and catch it in the controller:
in your form:
<%= hidden_field(:signup, :pass_confirm, :value => 'abcd') %>
then in the controller:
params[:signup]
There you can access a different model and validate the answer.
Action in the controller like:
def update
#company = Company.find(params[:id])
puts "extra field:"
puts params[:signup]
respond_to do |format|
if #company.update_attributes(params[:company])
format.html { redirect_to #company, :notice => 'Company was successfully updated.' }
format.json { head :ok }
else
format.html { render :action => "edit" }
format.json { render :json => #company.errors, :status => :unprocessable_entity }
end
end
end

Define accessors for the extra field and use usual ActiveRecord validations:
class MyModel < ActiveRecord::Base
attr_accessor :extra_field
validates :extra_field, :presence => true
validate :custom_validation_method
def custom_validation_method
errors.add :extra_field, :invalid unless extra_field == "correct"
end
end

Related

Rails 5 - Acts as Taggable On gem - defined tag lists

I am trying to learn how to use the Acts as Taggable On gem with Rails 5.
I have models called Proposal and Randd::Field. I am trying to tag proposals with tags which are the :title attribute of the Randd::Field table.
My models have:
Proposal
class Proposal < ApplicationRecord
acts_as_taggable_on :randd_maturities, :randd_fields, :randd_purposes, :randd_activities
# acts_as_taggable
# acts_as_taggable_on :skills, :interests
Randd::Field
(no association on Proposal).
Proposal helper
module ProposalsHelper
include ActsAsTaggableOn::TagsHelper
In my proposal form, I try to add tags:
<%#= f.select :tag_list %>
<%#= f.input :randd_field_list, collection: #randd_fields, label_method: :title, include_blank: false %>
<%= f.text_field :randd_field_list, input_html: {value: f.object.randd_field_list.to_s} %>
In my proposal controller, I have whitelisted an array of randd_field_list (which should hold each of the tags entered via the form).
def proposal_params
params.require(:proposal).permit(:title, :randd_maturity_list, :randd_fields_list,
I can add tags via the console. I cannot get this to work in the proposal form itself. In the console I can do:
p = Proposal.first
p.randd_field_list = [Randd::Field.last.title, Randd::Field.first.title]
p.save
This works to add the title of the first and last Randd::Fields to the array of tags on the proposal.
However, I can't figure out how to achieve this in the form. I get no errors showing in the rails s console. I cant see how to figure this out.
The Acts as Taggable On gem documentation this tutorial for editing tags - it suggests adding an update method to the Randd::Fields controller so that the tag can be updated. Taking that advice, I've tried to add the similar actions to my Randd::FieldsController as:
def edit
end
def update
#randd_field_list = ActsAsTaggableOn::Randd::Field.find(params[:id])
respond_to do |format|
if #randd_field_list.update(randd_field_list_params)
format.html { redirect_to root_path, notice: 'Tag was successfully updated.' }
format.json { render :show, status: :ok, location: #randd_field_list.proposal }
else
format.html { render :edit }
format.json { render json: #tag.errors, status: :unprocessable_entity }
end
end
This does nothing. I'm not sure if its a problem that I don't have a Tags Controller (at all), or if this is the generic label used for all controllers that are the tagging object. Is there anything required in the Proposal controller itself to handle the creation and updating of tags (which for my case are the titles of instances in the Randd::Field model?
Can anyone see what I need to do in order to use the tagging functionality provided by this gem? If I can do it in the console, it follows that I should be able to do it in the code - but its entirely unclear to me as to how to go about implementing this.
def proposal_params
params.require(:proposal).permit(:title, randd_maturity_list: [], randd_field_list:[]
end
You need to permit list params as an array, and make the tag list field on form to pass an array instead of text

Rails render json validation issue

Two issues here. First is that I need to access a model's id before all of its attributes are defined. Meaning that this:
class Search < ActiveRecord::Base
validates_presence_of :name
validates_presence_of :color_data
end
throws an error unless I removed the second line, which is not a good thing to do. My second issue is that I don't want to render json until a model has both attributes. This is my controller:
def create
#search = Search.create( name: (params[:name]) )
Resque.enqueue(InstagramWorker, #search.id)
respond_to do |format|
if #search.save
format.json { render json: #search }
format.html { redirect_to root_path }
else
format.html { redirect_to root_path }
end
end
end
Should I write some logic in the model to check for name && color_data before saving? And is there a workaround for accessing an id without breaking validations?
You probably can use conditional validations, like
class Search < ActiveRecord::Base
validates_presence_of :name
validates_presence_of :color_data, if: :some_condition?
private
def some_condition?
# condition logic here
end
end
You can't do this.
By calling Resque.enqueue(InstagramWorker, #search.id) you're telling resque to do something, but not as part of this request. So this could complete now, it could complete in 2 hours from now.
If you need to ensure that this completes before the request has finished, take it out of Resque.
What you could do is only validate the color_data on update, rather than create. Presumably your resqueue job calls #search.save. So by adding
validates :color_data, presence: true, on: :update
But this wouldn't stop the json being rendered, you can't get past the fact that this is not part of the request without taking it out of resqueue.

Rails: Conditional Validation & Views

What I'm thinking right now is...
I have a library full of books (entries). Each book has many checkouts (embedded document).
What I think I want to do is, upon checkout, make a new "checkout" as an embedded document. Upon checkin, I want to edit the checkout and add a "date_checked_out" field...
The issue is, my current model/controller makes a new entry each time there is a checkin or checkout...so it's doubly redundant...
What's the best way to go about this? Need more detail?
Checkout Controller:
def new
#entry = Entry.find(params[:entry_id])
#checkout = #entry.checkout.new
respond_to do |format|
format.html {render :layout => false}
end
end
def create
#entry = Entry.find(params[:entry_id])
#entry.update_attributes(:checked_out => "Out")
#checkout = #entry.checkout.create!(params[:checkout])
redirect_to "/", :notice => "Book Checked Out!"
end
class Checkout
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::MultiParameterAttributes
field :checkout_date, :type => Time
field :checkout_date_due, :type => Time
field :book_in, :type => Time, :default => Time.now
field :book_out, :type => Time, :default => Time.now
embedded_in :entries, :inverse_of => :entries
end
It makes sense the checkout would have a start and stop date. Do you need to make a checkout when a checkin occurs? You may be able to change this to an 'update' instead of a 'create' on the checkout controller - enter a checked_in_at on update.
Specifically - you'd want to be able to accept a PUT on the checkout controller - this could either be generic (allowing you to update the checkout in many ways) or specific, make a route that cleans up this for you:
resources :checkouts do
put :checkin, :on => :member
end
in checkouts_controller
def checkin
#checkout = Checkout.find(params[:id]
#checkout.update_attribute(:checked_in_at, Time.now)
# handle issues, redirect, etc.
end
Keeping it pure REST, add an update action to your Checkout controller.
Also, post your entry model. I'm assuming from your code that an entry has_one checkout, and a checkout belongs to an entry.
Something like:
*Edit because it appears OP wants to see how this works while checking for a conditional
... original boilerplate code ommitted
def update
#entry = Entry.find(params[:entry_id])
# if the book is checked out
if #entry.checked_out == "out"
# then update it
#entry.update_attributes(:checked_out => "whatever" # though I'd seriously consider changing checked out to a boolean - if it's either out or in, true or false makes sense. Ignore this advice if there are more than two states
#checkout = #entry.checkout
respond_to do |format|
if #checkout.update_attributes(:checked_out => "newValue")
...
else
.. handle errors
end
end
else
#the book does not have the correct status
respond_to do |format|
format.html { redirect_to some_action_path, :notice => "Entry is not out, so we cannot update its status." }
format.json { render json: #entry.errors, status: :unprocessible_entry }
end
end
end
Also, if you want to make the code a bit more explicit, you might consider taking swards advice and creating a few named endpoints like
def checkout
end
def checkin
end
I think that makes sense, in that someone else reading the code can very easily know exactly what that controller action is doing, as opposed to create and update.

Update fails with namespaced model

I have an update form in Rails 3 for admin users that fails silently, despite having validations. It was working previously, but when I moved everything to a namespace, it no longer saves.
Here is the relevant code from my controller:
def update
#admin = Admin::Admin.find(params[:id])
respond_to do |format|
if #admin.update_attributes(params[:admin])
flash[:success] = "'#{#admin.name}' was successfully updated."
format.html { redirect_to admin_admins_path }
else
format.html { render action: "edit" }
end
end
end
And the model (unfinished, but previously working):
class Admin::Admin < ActiveRecord::Base
validates :name, :presence=>{:message=>"Name can't be blank"}
validates :email, :presence=>{:message=>"Email can't be blank"},
:length => {:minimum => 3, :maximum => 254, :message=>"Email must be between 3 and 254 characters"},
:uniqueness=>{:message=>"Email has already been registered"},
:format=>{:with=>/^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message=>"Email must be a valid email format"}
validates :password, :presence=>{:message=>"Password can't be blank"}
end
And the first part of the form partial:
<%= form_for(#admin) do |f| %>
Everything loads properly, but when I try to save, my validations are ignored and it redirects to the index page with a success message, but without saving the data. I have a feeling I'm missing something to do with namespaces, but I'm not completely sure what the problem is. Could it be looking for the model in the base model directory?
Did you inspect the params? I could imagine that params[:admin] does not contain the forms values anymore.
So, VirtuosiMedia and I stepped through it, and RoR adds an "admin_" to represent the Admin:: namespace, so we had to look for params[:admin_admin].

Writing proper validation errors?

Update : Gutted entire question with more thorough description
Ok same question with different names.
In my model, I do validate the presence of.
class QuickFact < ActiveRecord::Base
belongs_to :organization
validates_presence_of :quick_fact, :content
But if either is blank, it errors out with :
Missing template organizations/_quick_fact_fields.erb
Here's the catch. I have a nested form model with dynamically addable parts to it. As followed from here :
http://railscasts.com/episodes/197-nested-model-form-part-2
That is what generates and calls the _quick_fact_fields.erb . But that works perfectly and is located within quick_facts/_quick_fact_fields.html.haml
Update: My Controller Code
organizations_controller.rb
def update
if #organization.update_attributes(params[:organization])
..
elsif params[:organization][:quick_facts_attributes]
flash[:notice] = 'QuickFacts successfully updated.'
redirect_to organization_quick_facts_url(#organization)
else
flash[:notice] = 'Organization was successfully updated.'
redirect_to :action => 'edit'
end
else
# re-render last form
..
elsif params[:organization][:quick_facts_attributes]
render :template => "quick_facts/index"
else
render :action => 'edit'
end
end
end
It seems that you're trying to render a my_custom_field partial from one of the worker views found in app/views/worker, but apparently there's no such partial there. If you show us the code of the relevant views and controllers, it will be easier to pinpoint the exact problem.
On a side note, you could simply do validates_presence_of :name instead of defining a custom validation method to simplify your model. However, this is likely unrelated to the error you're describing and is just a general improvement suggestion.
Got it. I had two controllers.
quick_facts_controller.rb, and organizations_controller.rb
Once I deleted the update function in quick_facts_controller, it worked properly.

Resources