problems with creating new item in nested resource - ruby-on-rails

I have nested resources as follows:
employer.rb
class Employer < ActiveRecord::Base
has_many :listings
end
listing.rb
class Listing < ActiveRecord::Base
belongs_to :employer
end
I built both using basic scaffold generator.
Everything seems to work except when I create a new listing for employer. The route is
new_employer_listing GET
/company/:employer_id/listings/new(.:format)
{:action=>"new", :controller=>"listings"}
When I navigate to the new listing url (company/employer_id/listings/new)
I get:
NoMethodError in ListingsController#new
undefined method `listing' for #<Employer:0x102dd5e48>
Here is the listings_controller code for #new
def new
#employer = Employer.find_by_username(params[:employer_id])
#listing = #employer.listing.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #listing }
end
end
Again, everything else works (show, edit, etc) -- I just can't get a new listing page to come up... Any help would be awesome.
Thanks!
//EDIT BELOW
def create
#employer = Employer.find_by_username(params[:employer_id])
#listing = #employer.listings.new(params[:listing])
respond_to do |format|
if #listing.save
format.html { redirect_to(#listing, :notice => 'Listing was successfully created.') }
format.xml { render :xml => #listing, :status => :created, :location => #listing }
else
format.html { render :action => "new" }
format.xml { render :xml => #listing.errors, :status => :unprocessable_entity }
end
end
end
ERROR:
No route matches {:action=>"show", :controller=>"listings", :id=>#<Listing id: 20, job_title: "asd", location: nil, status: nil, industry: nil, years: nil, degree_type: nil, degree_field: nil, employer_id: 1, employers_id: nil, user_id: nil>}

In the Employer model you have added the has_many :listings association. But in your controller you call #employer.Listing.new. The thing that does not match here is Listing and listings.
You should instead do it like this:
#listing = #employer.listings.new(params[:listing)
Sidenotes:
Don't forget to call save on the #listing, otherwise it will not get saved.
I prefer to use build instead of new, that way the listing is available in the Employer association directly. Both methods valid though depending on how you use them.

The employer has_many listings, so you have to call #employer.listings, with an s.

Related

How to properly associate a model with multiple models?

I've tried to implement many of the proposed solutions in the relevant questions, but haven't yet found an answer ideal for what I'm trying to achieve in my Rails 4 application.
Basically my app has three models. Users, Hooks (embeddable pop-up widgets) and Contacts. Users can create Hooks and Contacts within their interface. And any visitor can create a new contact by filling out the Contact create form placed within a Hook's view, and that contact is associated with the user who created that hook.
That works fine, however when a contact is created by filling out a Hook's form, there's no connection to the specific Hook they completed the form in.
The next set of features I would like to add to my app requires not only associating each contact with a user, but also with the specific Hook it was created from.
I've read a bit into polymorphic associations (model belongs to multiple models) and I understand that's probably the way to go. After a couple of failed attempts, I'm not sure how to implement it though.
How would I associate Contacts with Hooks, so users can know which hook a contact was created from?
Here is what I currently have in the Hooks controller and model...
def create
#hook = hook.new(hook_params)
#hook.user = current_user
#contact = Contact.new(contact_params)
respond_to do |format|
if #hook.save
format.html { redirect_to #hook, notice: 'Hook was successfully created.' }
format.json { render :show, status: :created, location: #hook }
format.js
else
format.html { render :new }
format.json { render json: #hook.errors, status: :unprocessable_entity }
format.js
end
end
end
class Hook < ActiveRecord::Base
belongs_to :user
has_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" }, :default_url => "https://s3.amazonaws.com/app/assets/leadmagnet.png"
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
end
And here is the contacts controller and model...
def create
#contact = Contact.new(contact_params)
#contact.user = current_user
respond_to do |format|
if #contact.save
if user_signed_in?
format.html { redirect_to #contact, notice: 'Contact was successfully created.' }
else
format.html { redirect_to #contact, notice: 'Contact was successfully created.' }
end
format.json { render :show, status: :created, location: #contact }
else
format.html { render :new }
format.json { render json: #contact.errors, status: :unprocessable_entity }
end
end
end
class Contact < ActiveRecord::Base
belongs_to :owner, :class_name => 'User'
belongs_to :user
validates :email, :presence => {:message => 'Email cannot be blank'}
end
First off, you should never ever ever create 2 unrelated models on the same controller action. It breaks conventions and will only lead to problems.
You do not need to directly associate Contacts to Users. You should associate Contacts to Hooks and then associate Contacts through Hooks
class User < ActiveRecord::Base
has_many :hooks
has_many :contacts, through: :hooks
end
class Hook < ActiveRecord::Base
belongs_to :user
has_many :contacts
accepts_nested_attributes_for :contacts
end
class Contact < ActiveRecord::Base
belongs_to :hook
end
Now on the create action of the ContactsController, you can first get the Hook either by URL param or passed via post body. You can first find the Hook and create the Contact on it via:
hook = Hook.find(hook_id)
#contact = hook.contacts.new(contacts_param)
If you want to create contacts when creating a new Hook, you need to add :contacts_attributes on the strong_params, then pass an array of contact attributes via the POST. Adding accepts_nested_attributes_for to the Hook model allows you to easily create Contacts while creating Hooks by simply entering:
#hook = Hook.new(hook_params)
If I understand correctly, you want to create both a Hook and a Contact, and associate both to current_user. In your code you create both, but you only associate #hook with the current_user, and only save it, while ignoring the #contact. Simply associate it and save it as well:
def create
#hook = hook.new(hook_params)
#hook.user = current_user
#contact = Contact.new(contact_params)
#contact.user = current_user
respond_to do |format|
if #hook.save && #contact.save
format.html { redirect_to #hook, notice: 'Hook was successfully created.' }
format.json { render :show, status: :created, location: #hook }
format.js
else
format.html { render :new }
format.json { render json: #hook.errors + #contact.errors, status: :unprocessable_entity }
format.js
end
end
end

I am trying to duplicate an activerecord record, but receive NoMethodError - undefined method

Forgive my ignorance but I am quite new to RoR. I am working on a project where users are able to duplicate a post in order to edit this "cloned version" and to save it (with a new post id, of course).
First I tried to use the Amoeba gem described like here, but I failed.
Then I thought I found a better solution - Duplicating a record in Rails 3 - but when I am integrating the suggested code, I am receiving the following error:
NoMethodError in Posts#show
undefined method `clone_post_path' for #<#:0x0000010267b8c8>
Researching and tinkering for hours now, I would really appreciate any help!
I am using Rails 3.2.13.
In my posts_controller I have the following code:
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
def new
#post = current_user.posts.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #post }
end
end
def clone
#post = current_user.posts.find(params[:id]) # find original object
#post = current_user.posts.new(#post.attributes) # initialize duplicate (not saved)
render :new # render same view as "new", but with #post attributes already filled in
end
def create
#post = current_user.posts.new(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render json: #post, status: :created, location: #post }
else
format.html { render action: "new" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
This is the post.rb model:
class Post < ActiveRecord::Base
attr_accessible :content, :title, :videos, :link, :description
validates :title, presence: true
belongs_to :user
end
In the show view I call this:
<%= link_to 'Create a clone', clone_post_path(#post) %>
What am I doing wrong?
Thank you so much in advance for any help!
UPDATE:
Adding
resources :posts do
get 'clone', on: :member
end
to the routes file worked.
Here is the routes file:
Tt::Application.routes.draw do
devise_for :users
get 'about' => 'pages#about'
resources :posts
root :to => 'pages#home'
post 'attachments' => 'images#create'
resources :posts do
get 'clone', on: :member
end
end
Unfortunately afterwards a new error occurred:
ActiveModel::MassAssignmentSecurity::Error in PostsController#clone
Can't mass-assign protected attributes: id, created_at, updated_at, image_file_name, image_content_type, image_file_size, image_updated_at, file, user_id
Make sure that your routes file includes the following:
resources :posts do
get 'clone', on: :member
end
Since the clone action is not a standard action you must account for it in your routes file so it knows what to do.

Rails getting around the edit method

I have two models: Photo and Product. These are associated via has_many and nested in.
In my 'create' action in my product controller I associate all the photos the user uploaded that have not been linked to a product. (I do it this way because photos are added via ajax).
Now on the edit page. I want to add photos. Which would require me to link photos to a product. Before I did that in the create action. But as you know in Rails there is no real edit action. Because of this there is nowhere in my products controller I join the two.
So, how do I get around this?
P.S. I can't join the two in the photo controller before you ask
product controller
def new
Photo.where(:product_id => nil, :user_id => current_user).delete_all
#product = Product.new
#photo = Photo.new
end
def create
binding.pry
#product = current_user.products.create(params[:product])
if #product.save
Photo.where(:product_id => nil, :user_id => current_user).update_all(:product_id => #product.id)
render "show", notice: "Product created!"
else
render "new", error: "Error submitting product"
end
end
def edit
#product = Product.find(params[:id])
#photo = Photo.new
end
photo controller
def create
#photo = Photo.new(params[:photo])
respond_to do |format|
#photo.user_id = current_user.id
if #photo.save
format.html {
render :json => [#photo.to_jq_image].to_json,
:content_type => 'text/html',
:layout => false
}
format.json { render json: {files: [#photo.to_jq_image]}, status: :created, location: #photo }
else
format.html { render action: "new" }
format.json { render json: #photo.errors, status: :unprocessable_entity }
end
end
end
There is a method for that
def update
end
exists
It may help if you used the scaffolding at least once to observe a typical rails controller. I'd pay attention to the methods generated.

Nice way of doing dual column validation

I'm using Rails 3 for this one. I've got a collections model, a user model and an intermediate subscription model. This way a user can subscribe to multiple collections, with a particular role. However, I don't want a user to be able to subscribe to the same collection twice.
So in my Subscription model I've got something like:
validate :subscription_duplicates
def subscription_duplicates
self.errors.add_to_base "This user is already subscribed" if Subscription.where(:user_id => self.user.id, :collection_id => self.collection.id)
end
However this seems ugly. Also, it breaks when I want to do something like the following in my collection controller:
def create
#collection = Collection.new(params[:collection])
#collection.subscriptions.build(:user => current_user, :role => Subscription::ROLES['owner'])
#collection.save
respond_with(#collection)
end
When I do the build the subscription does not have an id so I get a "Called id for nil" error.
Thanks for any guidance!
use validates_uniqueness_of
validates_uniqueness_of :user_id, :scope => :collection_id
First of all, your create action should always test if the object was saved, and if not then handle that (usually by re-rendering the new/edit page and showing the errors to the user).
A standard sort of create action would look like this (for a #post in this case):
def create
#post = Post.new(params[:post])
#created = #post.save
respond_to do |format|
if #created
flash[:notice] = 'Post was successfully created.'
format.html { redirect_to #post }
format.xml { render :xml => #post, :status => :created, :location => #post }
format.js
else
format.html { render :action => :new } #or edit or wherever you got here from
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
format.js
end
end
end
Shingara's approach to avoiding duplicates should work fine for you.

Virtual attributes on new models in Rails?

This certain part of my application takes cares of creation of Webshop model for store chains (like H&M) that has one. If the chain has a website that also is a webshop it creates one Webshop model.
If the website is not a webshop then it lets it be just at string in the Chain model.
PROBLEM: I'm doing this with a checkbox and virtual attributes. So when sending the a request to the chain controller a checkbox sets the value 'set_webshop'.
# Chain Model
class Chain
has_one :webshop, :dependent => :destroy
def set_webshop
self.webshop.url == self.website unless self.webshop.blank?
end
def set_webshop=(value)
if self.webshop.blank?
value == "1" ? self.create_webshop(:url => self.website) : nil
else
value == "1" ? nil : self.webshop.destroy
end
end
end
# Chain Controller
class ChainsController < ApplicationController
def create
#chain = Chain.new(params[:chain])
respond_to do |format|
if #chain.save
flash[:notice] = 'Chain was successfully created.'
format.html { redirect_to(#chain) }
format.xml { render :xml => #chain, :status => :created, :location => #chain }
else
format.html { render :action => "new" }
format.xml { render :xml => #chain.errors, :status => :unprocessable_entity }
end
end
end
def update
params[:chain][:brand_ids] ||= []
#chain = Chain.find(params[:id])
respond_to do |format|
if #chain.update_attributes(params[:chain])
flash[:notice] = 'Chain was successfully updated.'
format.html { redirect_to(#chain) }
format.js
else
format.html { render :action => "edit" }
end
end
end
end
It all works perfectly when updating a Chain model but when not when creating a new one? I can't figure out why?
Here are the POST and PUT requests.
# POST (Doesn't work - does not create a Webshop)
Processing ChainsController#create (for 127.0.0.1 at 2010-02-06 11:01:52) [POST]
Parameters: {"commit"=>"Create", "chain"=>{"name"=>"H&M", "set_webshop"=>"1", "website"=>"http://www.hm.com", "desc"=>"...", "email"=>"info#hm.com"}, "authenticity_token"=>"[HIDDEN]"}
# PUT (Works - does create a Webshop)
Processing ChainsController#update (for 127.0.0.1 at 2010-02-06 11:09:13) [PUT]
Parameters: { "commit"=>"Update", "chain"=> { "name" => "H&M", "set_webshop"=>"1", "website" => "http://www.hm.com", "desc" => "...", "email" => "info#hm.com"}, "authenticity_token"=>"[HIDDEN]", "id"=>"444-h-m"}
Is there a special way to handle virtual_attributes on new models in Rails?
It probably doesn't work because in this line
self.create_webshop(:url => self.website)
to create a webshop for the new chain you have no id of the chain yet (it hasn't been created at this moment), so there's no possibility to create an association.
Define an after_save callback and create a webshop there. To remember the value of the checkbox in the meantime, you can store in an attr_accessor.

Resources