Rails 4 validates uniqueness: {scope: :parent_id} for nested resource - ruby-on-rails

I was wondering whether anybody felt kind enough to help me figure out why this isn't working.
I have a Model lets call it Task which belongs to a Project Model. I basically want each Task to have a unique name per project (Project1 could have a task called task1 and so could Project2 but both could only have one called task1) . This seems to be what the :scope option is for but it doesn't seem to be working for me.
The task model is a nested resource within project and as such I call the create action via project_tasks_path(#project). It works fine creating tasks and assigning them to projects but the scope of the uniqueness validation is not taking hold. If I create a task task1 in Project1 I can't create one with the same name in task 2.
This is my setup:
Task.rb
class Task < ActiveRecord::Base
belongs_to :project
validates :name, presence: true, uniqueness: {scope: :project_id}
tasks_controller.rb
def create
#project = Project.find_by(id: params[:project_id])
#task = Task.new(model_params)
#print task to stdout
puts "#task"
ap #task
respond_to do |format|
if #task.save
flash[:notice] = "Successfully created task"
format.js
else
# no flash as form handles errors
format.js { render action: 'new' }
format.html { render action: 'new' }
end
end
end
for some reason when I output the contents of the newly created task, I get the following
#<Task:0x007ff7c7c3b178> {
:id => nil,
:name => "test",
:project_id => nil,
:created_at => nil,
:updated_at => nil
}
It seems that because project_id hasn't been set at this point it's using 'nil' as the value.
What's the best way to get around this? would it just be a custom validator?
Edit 1
def model_params
params.require(:model).permit(:name, :project_id)
end

Right, having been playing around with this, it seems that the way to make this type of validation is pretty straight forward. All it requires is that the nested resource be built in relation to it's project, this forces the :parent_id to be passed through to the validation as expected.
In the case of this toy example, that means that the create action has to look something like:
#project = Project.find_by(id: params[:project_id])
#task = #project.tasks.build(model_params)
It should be noted that because of Rails not supporting generation of nested resources from the command line, the way that the scaffold generated controllers handle creation is by Model.new(model_params) and then saving, this doesn't seem to pick up the :parent_id in time for the validation and so will need changing as above (in terms of the parent).

Related

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.

How to create and and edit nested objects in rails 3.2?

So I have two models, Reports and Receipts. Each report has many receipts. I used scaffolding to generate all my views and stuff but Im changing things around so that when a user creates a new report or edits one, they can create and edit receipts in the form.
My models are set up:
class Report < ActiveRecord::Base
has_many :receipts, :dependent => :destroy
accepts_nested_attributes_for :receipts, :allow_destroy => true
attr_protected :id
end
class Receipt < ActiveRecord::Base
belongs_to :report
attr_protected :id
validates_presence_of :vendor, :date, :description, :amount, :acctCode
end
I have the form set up to create a new receipt:
<%= form_for #report do |f| %>
....
<%= f.fields_for :receipts, Receipt.new do |receipt| %>
...
<% end %>
<% end %>
But every time I go to save a report, I get a routing error:
No route matches {:action=>"edit", :controller=>"receipts", :report_id=>#<Receipt id: nil, date: nil, vendor: "", description: "", amount: nil, companyCard: false, lobbyingExpense: false, acctCode: "", created_at: nil, updated_at: nil, report_id: 2>}
and my routes are set up as:
resources :reports do
resources :receipts
end
and my controller for receipts has
# GET /receipts/new
def new
#receipt = Receipt.new
respond_to do |format|
format.html # new.html.erb
end
end
# GET /receipts/1/edit
def edit
#receipt = Receipt.find(params[:id])
end
# POST /receipts
def create
#receipt = Receipt.new(params[:receipt])
respond_to do |format|
if #receipt.save
format.html { redirect_to #receipt.Report, notice: 'Receipt was successfully created.' }
else
format.html { render action: "new" }
end
end
end
I havent touched rails in a while so Im not sure what Im doing wrong. But in my older apps (3.1) when I added images to say, blog posts, I didnt even have a controller for images other than to delete them via ajax. The only reason I have a controller here for receipts is because I used scaffolds to generate the views and such.
edit - I should also point out, that if I go to the new receipt view, I get an error on the form tag:
<%= form_for(#receipt) do |receipt| %>
undefined method `receipts_path'
If you are using accepts_nested_attributes_for you don't need an extra controller to manage the records. Of course if you need specific pages like a "show view" for a receipt you need that controller.
To get accepts_nested_attributes_for you need:
A form for your report
use fields_for :receipts in that form
This way you can edit all created receipts for a given report. If you also want to create new receipts you can add a blank receipt with: #report.receipts.build. You can add this call to your new and edit actions.
Note that you edit the receipts in a form for the report. This means, that you should hit the ReportsController and not the ReceiptsController.
If things do not work here is some debugging advice:
execute rake routes to see if everything is defined correctly.
Inspect the generated HTML from form_for(#report). Especially the 'action=""' attribute of the form tag is relevant. It should point to "/reports/X"
EDIT: I created a Gist with all the relevant files to get a nested form working: https://gist.github.com/4420280
Do checkout cocoon gem for nested resources form. This gem has made a work lot easier dealing with nested resources. https://github.com/nathanvda/cocoon

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].

Can I access information from one associated AR object in another when both are unsaved?

Say I open a Rails (2.3.8) script console and try this:
a = Account.new(:first_name) = 'foo'
i = a.invoices.build
p i.account.first_name
Account.rb is a model object and contains:
has_many :invoices
and Invoice.rb is a model as well containing:
belongs_to :account, :validate => true
In console line 3 above, i.account is nil. I realize that i.account would not be nil if account had been saved, but I do not wish to save an account unless I can create a valid invoice for the account. And, just for kicks, the invoice validation depends on some properties of the unsaved account.
Any ideas how to make this work?
Best,
Will
I typically do this with transactions. With rails transactions you can perform db interactions and roll them back at any time if something fails to validate. For example:
in your model:
def save_and_create_invoice
Account.transaction do
#first let's save the account, this will give us an account_id to work with
return false unless self.save
invoice = self.invoices.build
#setup your invoice here and then save it
if invoice.save
#nothing wrong? return true so we know it was ok
return true
else
#add the errors so we know what happened
invoice.errors.full_messages.each{|err| errors.add_to_base(err)}
#rollback the db transaction so the account isn't saved
raise ActiveRecord::Rollback
#return false so we know it failed
return false
end
end
end
And in your controller you would call it like so:
def create
#account = Account.new(params[:account])
respond_to do |format|
if #account.save_and_create_invoice
format.html
else
format.html {render :action => "new"}
end
end
end
Note that I didn't run this code to test it, just whipped it out real quick to show an example.

Resources