I am working on an admin interface where I have images and heroes. The hero table consists in only two columns: id and image_id. I would like to be able to add and remove images to the hero table.
I have a select_to_hero and select_from hero action and view which display either all images not already connected or all existing heroes and both work, but the add_to_hero and remove_from_hero actions, which I use to create a new or destroy an existing association do not work.
Hero.rb Model
class Hero < ActiveRecord::Base
attr_accessible :image_id
belongs_to :image
end
Image.rb Model
class Image < ActiveRecord::Base
attr_accessible :alt, :author, :copyright, :file_name, :title
has_one :hero
mount_uploader :file_name, ImageUploader
end
Select_from_hero.html.erb
<% #heroes.each do |hero| %>
<%= link_to(image_tag(hero.image.file_name.url(:thumb), {:action => 'remove_from_hero', :id => hero, :hero => #hero}) %>
<% end %>
Select_to_hero.html.erb
<% #images.each do |image| %>
<%= link_to(image_tag(image.file_name.url(:thumb), {:action => 'add_to_hero', :id => image, :hero => #hero}) %>
<% end %>
images_controller.rb
def add_to_hero
#hero.image << Image.find(params[:id]) unless #hero.image.include?(Image.find(params[:id]))
if #hero.save
..
else
render :action => 'select_to_hero'
end
end
def remove_from_hero
#hero.image.delete(Image.find(params[:id]))
if #hero.save
..
else
render :action => 'select_from_hero'
end
end
With this setting I get:
NoMethodError in Admin::ImagesController#add_to_hero
undefined method `image' for nil:NilClass
and
NoMethodError in Admin::ImagesController#remove_from_hero
undefined method `image' for nil:NilClass
But I can query an existing association:
> Hero.find(2).image
Hero Load (0.3ms) SELECT `heroes`.* FROM `heroes` WHERE `heroes`.`id` = ? LIMIT 1 [["id", 2]]
Image Load (0.3ms) SELECT `images`.* FROM `images` WHERE `images`.`id` = 1 LIMIT 1
=> #<Image id: 1, file_name: "red.jpg", title: "blu", alt: "yellow", author: "John", copyright: "Jane", created_at: "2019-01-29 19:50:25", updated_at: "2019-01-29 19:50:25">
How can I get this working?
Update
Routes
namespace :admin do
resources :heroes
match '/images/select_to_hero', :to => 'images#select_to_hero', :as => :select_to_hero
match '/images/select_from_hero', :to => 'images#select_from_hero', :as => :select_from_hero
resources :images
match '/images/add_to_hero/:id', :to => 'images#add_to_hero', :as => :add_to_hero
match '/images/remove_from_hero/:id', :to => 'images#remove_from_hero', :as => :remove_from_hero
...
end
I had to move the select_to_hero and select_from_hero routes above resources :images or a call would have triggered the show action.
follow up your last comment about your add_to_action, I would like to suggest solution below
Select_to_hero.html.erb, change :hero and send only the id of hero like this below
<% #images.each do |image| %>
<%= link_to(image_tag(image.file_name.url(:thumb), {:action => 'add_to_hero', :id => image, :hero_id => #hero.id}) %>
<% end %>
images_controller.rb, find the hero first from the hero_id
def add_to_hero
#hero = Hero.find(params[:hero_id])
#hero.image << Image.find(params[:id]) unless #hero.image.include?(Image.find(params[:id]))
if #hero.save
..
else
render :action => 'select_to_hero'
end
end
Thanks to the always helpful #vasilisa I figured out a way to solve my problem. I had to use:
<% #images.each do |image| %>
<%= link_to(image_tag(image.file_name.url(:thumb)), {:action => 'add_to_hero', :id => image, :image_id => image}) %>
<% end %>
in my view and:
def add_to_hero
#hero = Hero.new(:image_id => params[:id])
if #hero.save
...
else
render :action => 'select_to_hero'
end
end
in my controller action.
This works, since in my case I only needed to declare an image as a hero by creating a new entry into Hero or to remove a hero declaration by destroying an entry in Hero.
Thanks all for their advice.
Related
I have a shipping project where I can create ships and jobs. I have a many-to-many relationship with jobs and ships, but I'm having trouble creating new jobs for a ship.
I want a new link to assign jobs for a specific boat via check-boxes but I get an error when I click on the link to assign jobs when no route matches get "/ship/all_jobs".
This is my Ship model:
class Ship < ApplicationRecord
has_and_belongs_to_many :jobs
has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100#" }, :default_url => "/images/:style/missing.png"
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
validates :name, uniqueness: true
end
This is my Job model:
class Job < ApplicationRecord
has_and_belongs_to_many :ships
validate :price_has_to_be_greater_than_minimum
validates :title, uniqueness: true
validates :description, length: { minimum: 50 }
def price_has_to_be_greater_than_minimum
errors.add(:cost, "price has to be greater than 1000") if
!cost.blank? and cost > 1000
end
end
This is the Jobship join table:
class Jobship < ApplicationRecord
belongs_to :ships
belongs_to :jobs
end
and my ships controller:
def all_jobs
#ship = Ship.find(params[:id])
end
def create
#ship = Ship.new(ship_params)
if #ship.save
flash[:notice] = 'Ship record was successfully created.'
redirect_to(#ship)
else
render :action => "new"
end
end
def save
#ship = Ship.find(params[:id])
#job = Job.find(params[:job])
if params[:show] == "true"
#ship.jobs << #ship
else
#ship.jobs.delete(#ship)
end
#ship.save!
render :nothing => true
end
private
def ship_params
params.require(:ship).permit(:name, :location, :avatar)
end
end
This is the all_jobs view:
<h1>jobs for <%= #ship.name %></h1>
<table>
<tr>
<th>assignl</th>
<th>job</th>
</tr>
<%= form_for (#ship) do |f| %>
<%= f.label "jobs" %><br />
<%= f.collection_check_boxes :job_ids, Job.all, :id, :title do |b| %>
<div class="collection-check-box">
<%= b.check_box %>
<%= b.label %>
<%= f.submit %>
<%end%>
<%end%>
This is the link to all jobs from the index view:
<%= link_to 'New ship', new_ship_path %>
<% #ships.each do |ship| %>
<h1>ship name</h1><%=ship.name%><h1> ship location</h1> <%= ship.location %> <%= link_to 'Show', ship %>
<%= link_to 'jobs', ship_all_jobs_path(ship) %>
<%= link_to "all jobs", jobs_path %>
<% end %>
And my routes:
Rails.application.routes.draw do
devise_for :users
resources :ships
resources :jobs
root :to => "ships#index"
post "ship/all_jobs", :to=> "ships#save"
get "ship/all_jobs/:id", :to => "ships#all_jobs"
end
First of all you are going all wrong about many to many relationships in rails, you don't need separate model for the join table, active record takes care of it read here http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association to learn more; so you don't need the Jobship model.
class Jobship < ApplicationRecord
belongs_to :ships
belongs_to :jobs
end
You have to put / before you define your routes:
Rails.application.routes.draw do
devise_for :users
resources :ships
resources :jobs
root :to => "ships#index"
post "/ship/all_jobs", :to=> "ships#save"
get "/ship/all_jobs/:id", :to => "ships#all_jobs"
end
also you can see your defined routes using rails routes in the command line.
models/lead.rb
class Lead < MailForm::Base
attribute :fullname
def headers
{
:subject => "My Contact Form",
:to => "callumshorty#hotmail.com",
:from => "admin#uk-franchise.co.uk"
}
end
end
controllers/lead_form_controller.rb
class LeadFormController < ApplicationController
def new
#lead = Lead.new
end
def create
#lead = Lead.new(params[:lead_form])
#lead.request = request
#lead.deliver
end
end
routes.rb
resources :lead_form
views/listings/show.html.erb
<%= form_for #lead, :url => url_for(:controller => 'lead_form', :action => 'new') do |lead| %>
<%= lead.text_field :fullname %>
<%= lead.submit %>
<% end %>
The error when trying to access the show page:
First argument in form cannot contain nil or be empty
On line:
<%= form_for #lead, :url => url_for(:controller => 'lead_form', :action => 'new') do |lead| %>
Any help would be super appreciated, can't stand these mailers :(
Your #lead needs to be initialised in the ListingsController#show action, since your lead form is in that view. Try adding #lead = Lead.new in the ListingsController#show method.
When I try to subscribe to a product by clicking the link:
<%= link_to "Subscribe", :controller => "products", :action => "subscribe_product", :id => product.id, :method => :post %>
I get this error and notice the parameters are wrong.
ActiveRecord::RecordNotFound in ProductsController#show
Couldn't find Product with id=subscribe_product
{"id"=>"subscribe_product", "method"=>"post"}
My subscribe_product method in my ProductsController is:
def subscribe_product
#product = Product.find(params[:id])
#product.subscriptions.create(:subscriber_id => current_user.id)
end
My route:
resources :products do
post :subscribe_product, :on => :collection
end
These are the associations:
class User
has_many :products
has_many :subscriptions, :foreign_key => :subscriber_id
class Product
belongs_to :user
has_many :subscriptions, :as => :subscribable
class Subscriptions
belongs_to :subscriber, :class_name => "User"
belongs_to :subscribable, :polymorphic => true
Users subscribe in another controller:
PagesController
def index
#product_history = current_user.products
end
end
pages/index.html.erb
<% for product in #product_history %>
<%= product.name %>
<%= product.price %>
<%= link_to "Subscribe", :controller => "products", :action => "subscribe_product", :id => product.id, :method => :post %>
<% end %>
So why is my action method being seen as the ID instead?
Try :
resources :products do
post :subscribe_product, :on => :member
end
It will generate routes like :
subscribe_product_product POST /product/:id/subscribe_product(.:format) {:action=>"subscribe_product", :controller=>"products"}
and use path like in view :
subscribe_products_path(product.id)
Since you're passing an id, the subscribe_product route should be a member route. Try this, and let me know what you get:
resources :products do
member do
post 'subscribe_product'
end
end
In the controller (to get around non-mass-assignable attributes):
def subscribe_product
#product = Product.find(params[:id])
subscription = Subscription.new
subscription.subscriber_id = current_user.id
#product.subscriptions << subscription
end
Please try this. Change your route to :
resources :products do
post :subscribe
end
Then change your link like :
<%= link_to "Subscribe", subscribe_products_path(:id => product.id), :method => :post %>
I can't seem to find an example that is complete in all the components. I am having a hard time deleting image attachments
Classes
class Product
has_many :product_images, :dependent => :destroy
accepts_nested_attributes_for :product_images
end
class ProductImage
belongs_to :product
has_attached_file :image #(etc)
end
View
<%= semantic_form_for [:admin, #product], :html => {:multipart => true} do |f| %>
<%= f.inputs "Images" do %>
<%= f.semantic_fields_for :product_images do |product_image| %>
<% unless product_image.object.new_record? %>
<%= product_image.input :_destroy, :as => :boolean,
:label => image_tag(product_image.object.image.url(:thumb)) %>
<% else %>
<%= product_image.input :image, :as => :file, :name => "Add Image" %>
<% end %>
<% end %>
<% end %>
<% end %>
Controller
class Admin::ProductsController < AdminsController
def edit
#product = Product.find_by_permalink(params[:id])
3.times {#product.product_images.build} # added this to create add slots
end
def update
#product = Product.find_by_permalink(params[:id])
if #product.update_attributes(params[:product])
flash[:notice] = "Successfully updated product."
redirect_to [:admin, #product]
else
flash[:error] = #product.errors.full_messages
render :action => 'edit'
end
end
end
Looks good, but, literally nothing happens when I check the checkbox.
In the request I see:
"product"=>{"manufacturer_id"=>"2", "size"=>"", "cost"=>"5995.0",
"product_images_attributes"=>{"0"=>{"id"=>"2", "_destroy"=>"1"}}
But nothing gets updated and the product image is not saved.
Am I missing something fundamental about how 'accepts_nested_attributes_for' works?
From the API docs for ActiveRecord::NestedAttributes::ClassMethods
:allow_destroy
If true, destroys any members from the attributes hash with a _destroy key and a value that evaluates to true (eg. 1, ‘1’, true, or ‘true’). This option is off by default.
So:
accepts_nested_attributes_for :product_images, allow_destroy: true
I'm trying to use rails nested form_for helper, but I am getting the following error:
BlogPage(#49859550) expected, got Array(#31117360)
Here are my model objects:
class Blog < ActiveRecord::Base
# Table Configuration
set_table_name "blog"
# Model Configuration
belongs_to :item
has_many :blog_pages
accepts_nested_attributes_for :blog_pages, :allow_destroy => true
end
class BlogPage < ActiveRecord::Base
# Table Configuration
set_table_name "blog_page"
# Model Configuration
belongs_to :blog
end
Here is the form I generated (left out unnecessary HTML):
<% form_for :blog, :url => { :action => :create } do |blog_form| %>
<%= blog_form.text_field :title, :style => "width: 400px" %>
<% blog_form.fields_for :blog_pages do |page_fields| %>
<% #blog.blog_pages.each do |page| %>
<%= page_fields.text_area :content, :style => "width: 100%",
:cols => "10", :rows => "20" %>
<% end %>
<% end %>
<% end %>
Here are the parameters that are sent to the controller:
{"commit"=>"Save",
"blog"=>{"blog_pages"=>{"content"=>"This is the new blog entries contents."},
"title"=>"This is a new blog entry.",
"complete"=>"1"},
"authenticity_token"=>"T1Pr1g9e2AjEMyjtMjLi/ocrDLXzlw6meWoLW5LvFzc="}
Here is the BlogsController with the create action that gets executed:
class BlogsController < ApplicationController
def new
#blog = Blog.new # This is the line where the error gets thrown.
# Set up a page for the new blog so the view is displayed properly.
#blog.blog_pages[0] = BlogPage.new
#blog.blog_pages[0].page_number = 1
respond_to do |format|
format.html # Goes to the new.html.erb view.
format.xml { render :xml => #blog }
format.js { render :layout => false}
end
end
def create
#blog = Blog.new(params[:blog])
respond_to do |format|
if #blog.save
render :action => :show
else
flash[:notice] = "Error occurred while saving the blog entry."
render :action => :new
end
end
end
end
If anyone can help me with this I would greatly appreciate it. I'm still pretty new to ruby and the rails framework and couldn't solve the problem on my own by googling.
Thanks.
Have you seen this?
http://media.pragprog.com/titles/fr_arr/multiple_models_one_form.pdf
Change your form to this:
<% form_for :blog, :url => { :action => :create } do |blog_form| %>
<%= blog_form.text_field :title, :style => "width: 400px" %>
<% blog_form.fields_for :blog_pages do |page_fields| %>
<%= page_fields.text_area :content, :style => "width: 100%",
:cols => "10", :rows => "20" %>
<% end %>
<% end %>
If you use fields_for it iterates over blog_pages automaticaly. However I'm not sure if this caused errors.