I've done this 10 times and every time I seem to run into some issue with nested forms. Here's what I have:
client controller:
def new
#client = Client.new
#contact = #client.contacts.new
#header = "New Client"
respond_to do |format|
format.html # new.html.erb
format.json { render json: #client }
end
end
client class:
class Client < ActiveRecord::Base
## ASSOCIATIONS ##
belongs_to :user
has_many :contacts, :dependent => :destroy
has_many :invoices
## ACCESSIBLE ##
attr_accessible :name, :address_line_one, :address_line_two,
:contacts_attributes
## NESTED ATTRIBUTES ##
accepts_nested_attributes_for :contacts
in the form:
= form_for(#client) do |f|
= f.fields_for(#contact) do |contact|
but i still get this error when submitting the form:
Can't mass-assign protected attributes: contact
and the params:
"client"=>{"name"=>"23",
"contact"=>{"name"=>"asdf",
"email"=>"af#gmail.com"}},
"commit"=>"Save"}
What I would do is:
def new
#client = Client.new
#client.contacts.build
#header = "New Client"
respond_to do |format|
format.html # new.html.erb
format.json { render json: #client }
end
end
And:
= form_for(#client) do |f|
= f.fields_for(:contacts) do |contact|
Related
I'm having an issue creating multiple option_values from a form param field called "name" that has a value that looks like this: a1,a2,b2,c4. What I would like to do is create an option_value for each one of those entries but I'm not sure how to do it. I know that I need to split the value but I'm just not sure where to do that exactly.
Controller:
class Admin::OptionValuesController < Admin::ApplicationController
before_action :set_option_value, only: [:show, :edit, :update, :destroy]
# GET /option_values
# GET /option_values.json
def index
#option_values = OptionValue.all
end
# GET /option_values/1
# GET /option_values/1.json
def show
end
# GET /option_values/new
def new
#option_value = OptionValue.new
end
# GET /option_values/1/edit
def edit
end
# POST /option_values
# POST /option_values.json
def create
#option_value = OptionValue.new(option_value_params)
respond_to do |format|
if #option_value.save
format.html { redirect_to #option_value, notice: 'Option value was successfully created.' }
format.json { render :show, status: :created, location: #option_value }
else
format.html { render :new }
format.json { render json: #option_value.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /option_values/1
# PATCH/PUT /option_values/1.json
def update
respond_to do |format|
if #option_value.update(option_value_params)
format.html { redirect_to #option_value, notice: 'Option value was successfully updated.' }
format.json { render :show, status: :ok, location: #option_value }
else
format.html { render :edit }
format.json { render json: #option_value.errors, status: :unprocessable_entity }
end
end
end
# DELETE /option_values/1
# DELETE /option_values/1.json
def destroy
#option_value.destroy
respond_to do |format|
format.html { redirect_to option_values_url, notice: 'Option value was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_option_value
#option_value = OptionValue.find(params[:id])
end
# Only allow a list of trusted parameters through.
def option_value_params
params.require(:option_value).permit(:option_type_id, :name)
end
end
Form. Within the main product form this allows me to create an option_type and also at that time create multiple option_values which belong to the option_type: This is what I am doing.
<div class="grid md:grid-cols-1 row-gap-6 col-gap-4 lg:grid-cols-3 mb-4">
<%= form.fields_for :option_types, OptionType.new do |options| %>
<div>
<%= options.label "Option Type Name", class: "text-gray-700" %>
<%= options.text_field :name, class: 'w-full mt-2 px-4 py-2 block rounded bg-gray-200 text-gray-800 border border-gray-300 focus:outline-none focus:bg-white' %>
</div>
<div data-controller="nested-form">
<%= options.fields_for :option_values, OptionValue.new do |ov| %>
<%= render "admin/option_types/option_values_fields", form: ov %>
<% end %>
</div>
<% end %>
</div>
Models:
product:
class Product < ApplicationRecord
has_many_attached :images, :dependent => :delete_all
has_one_attached :main_image, :dependent => :delete_all
has_many :product_option_types, dependent: :destroy, inverse_of: :product
has_many :option_types
has_many :option_values
accepts_nested_attributes_for :option_types
accepts_nested_attributes_for :option_values
has_many :variants, inverse_of: :product, dependent: :destroy
end
Option_type
class OptionType < ApplicationRecord
belongs_to :product
has_many :option_values, dependent: :destroy
accepts_nested_attributes_for :option_values, reject_if: :all_blank, allow_destroy: true
has_many :product_option_types, dependent: :destroy
end
option_value
class OptionValue < ApplicationRecord
belongs_to :option_type
has_many :option_value_variants, dependent: :destroy
has_many :variants, through: :option_value_variants
validates_presence_of :name
validates_uniqueness_of :name, scope: :option_type_id, case_sensitive: false
def create_from_csv(comma_separated_string)
comma_separated_string.split(',').map do |val|
create(name: val)
end
end
end
You can create a method in your OptionType called create_with_values
In it you'd do something like
def self.create_with_values(type, names)
type_instance = create(name: type)
names.split(',').map do |val|
type_instance.option_values.create(name: val)
end
end
Instead of your OptionValuesController, your OptionTypesController's create method should be
def create
OptionType.create_with_values(type: params[:type], values: params[:name])
redirect_to appropriate_path
end
A more railsy way to do this would be with nested attributes but you will have to setup your create_with_values method to transform the incoming comma separated strings into something that the nested attributes can accept.
Look at https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html for more information
I'm trying to figure out multi-uploads with CarrierWave and being able to save it from a different Controller and Model through accepts_nested_attributes_for :car_images, reject_if: :all_blank, allow_destroy: true
I'm using CarrierWave and my CarImage model contains a JSON type column for the image.
When creating a new car post CarsController#New I want to be able to:
Write title (working)
Write description (working)
Upload multiple images through nested forms (working)
I get the following error message when trying to save a new Car with images attached:
Unpermitted parameter: images
The params:
Started POST "/cars" for 127.0.0.1 at 2015-08-20 22:04:19 +1000
Processing by CarsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"GEAGEAKQw3foK7/1+kGMhTVArqRaC8gHaHjHAtef0zmJDr2i5U9TictQ9kj3A==", "car"=>{"title"=>"Ford", "description"=>"Mustang", "car_images_attributes"=>{"0"=>{"images"=>[#<ActionDispatch::Http::UploadedFile:0x007fbec0dfa5a8 #tempfile=#<Tempfile:/var/folders/r0/24wrt39d1kl37mmymjjmmxy45000gn/T/RackMultipart20150820-12757-1e4egob.jpg>, #original_filename="ford-mustang-front.jpg", #content_type="image/jpg", #headers="Content-Disposition: form-data; name=\"car[car_images_attributes][0][images][]\"; filename=\"ford-mustang-front.jpg\"\r\nContent-Type: image/jpg\r\n">, #<ActionDispatch::Http::UploadedFile:0x007fbec0dfa508 #tempfile=#<Tempfile:/var/folders/r0/24wrt39d1kl37mmymjjmmxy45000gn/T/RackMultipart20150820-12757-6pckqd.png>, #original_filename="ford-mustang-back.jpg", #content_type="image/png", #headers="Content-Disposition: form-data; name=\"car[car_images_attributes][0][images][]\"; filename=\"ford-mustang-back.jpg\"\r\nContent-Type: image/jpg\r\n">]}}}, "commit"=>"Create Car"}
I'm not sure if it's to do with the form, but when saving multiple files as an Array should it have unique indexes? The params log looks like it's saving all the images in the first index: car[car_images_attributes][0][images][]\
My CarsController the permits the following params:
def car_params
params.require(:car).permit(:title, :description,
car_images_attributes: [:id, :car_id, :image])
end
The car_images_attributes permits the :image attribute which is a JSON/Array type.
Any insight is much appreciated.
Car Model
# == Schema Information
#
# Table name: cars
#
# id :integer not null, primary key
# title :string default(""), not null
# description :text default("")
# price :float default(0.0)
# created_at :datetime not null
# updated_at :datetime not null
class Car < ActiveRecord::Base
has_many :car_photos
accepts_nested_attributes_for :car_images, reject_if: :all_blank, allow_destroy: true
end
Car Image Model
# == Schema Information
#
# Table name: car_images
#
# id :integer not null, primary key
# image :json default({}), not null
# title :string default(""), not null
# car_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class CarImages < ActiveRecord::Base
belongs_to :car
mount_uploader :image, ImageUploader
end
Car Controller
class CarsController < ApplicationController
before_action :set_car, only: [:show, :edit, :update, :destroy]
def show
end
def new
#car = Car.new
#car.car_images.build
end
def edit
end
def create
#car = Car.new(car_params)
respond_to do |format|
if #car.save
format.html { redirect_to #car, notice: 'Car was successfully created.' }
format.json { render :show, status: :created, location: #car }
else
#car.car_images.build # ensures car_image form object exists
format.html { render :new }
format.json { render json: #car.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #car.update(car_params)
format.html { redirect_to #car, notice: 'Car was successfully updated.' }
format.json { render :show, status: :created, location: #car }
else
format.html { render :new }
format.json { render json: #car.errors, status: :unprocessable_entity }
end
end
end
def destroy
#car.destroy
respond_to do |format|
format.html { redirect_to root_path, notice: 'Car was successfully destroyed.'}
format.json { head :no_content }
end
end
private
def set_car
#car = Car.find_by_id(params[:id])
end
def car_params
params.require(:car).permit(:title, :description, car_images_attributes: [:id, :car_id, :images])
end
end
Car Form
<%= simple_form_for #car, html: { multipart: true } do |f| %>
<%= f.input :title %>
<%= f.input :description, as: :text %>
<%= f.simple_fields_for :car_images do |image| %>
<% if image.object.new_record? %>
<%= image.input :images, as: :file, required: false, error: false, input_html: { multiple: true } %>
<% end %>
<% end %>
<%= f.button :submit %>
<% end %>
Any insights is much appreciated.
I was thinking of iterating through the params[:car_images]['image'] and creating a CarImage record, but i thought this might not be the best way, and would want to be able to add a Car record without images, and this would fail if it doesn't have an image attached unless extra checks (nil) is done.
Update
I've tried DB column types JSON and TEXT (as an array) but still have the problems. If I change it as a plain STRING it works but it's not a collection of photos.
I'm creating my own blog on Rails with posts and users. I need to show all posts from specific author when I click on him (here the concept:link). What should I do for this?
Please say what extra information or code should I add
users_controller:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#posts = #user.posts
end
end
posts_controller:
class PostsController < ApplicationController
before_filter :authenticate_user!, :except => [:show, :index]
# GET /posts
# GET /posts.json
def index
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
# GET /posts/1
# GET /posts/1.json
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
# GET /posts/new
# GET /posts/new.json
def new
#post = Post.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #post }
end
end
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.json
def create
##post = Post.new(params[:post])
#post = current_user.posts.build(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
# PUT /posts/1
# PUT /posts/1.json
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to #post, notice: 'Post was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url }
format.json { head :no_content }
end
end
end
user model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
has_many :posts, :dependent => :destroy
validates :fullname, :presence => true, :uniqueness => true
validates :password, :presence => true
validates :email, :presence => true, :uniqueness => true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :fullname
end
post model:
class Post < ActiveRecord::Base
attr_accessible :text, :title
validates :user_id, :presence => true
validates :title, :presence => true
validates :text, :presence => true
belongs_to :user
has_many :comments
end
This is a fairly straight forward use of Ruby on Rails. I recommend reading Active Record Basics to get up to speed.
First, you should have a belongs_to relationship between Posts and Users that looks like this:
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
end
This adds a .posts method to the User instance and a .user method to the Post instance.
Then you have to make a decision about how you want the URL structure of your application to work. Here are a few options from the top of my head:
/posts?user=:user_id
/posts/by/:user_id
/users/:id/posts
Given the relationship between a User and their Posts, my recommendation (and I believe the general "Rails Way") would be #3. So, let's add the routes to config/routes.rb:
The short way to create JUST that route:
get 'users/:id/posts' => 'users#posts', :as => :user_posts
The long way to create the route based on resources:
resources :users do
member do
get :posts
end
end
Both approaches will provide a helper method called user_posts_path and one called user_posts_url which can be used in your view to link to the list of user posts using the link_to helper method:
<%= link_to post.user.name, user_posts_path(post.user) %>
Now, you have to add the controller action in app/controllers/users_controller.rb:
class UsersController < ActionController::Base
def posts
#user = User.find(params[:id])
#posts = #user.posts
end
end
and then add your HTML/ERB code to app/views/users/posts.html.erb
<% #posts.each do |post| %>
<%= post.inspect %>
<% end %>
That should give you the basic ability to show a user's posts. You can enhance it by reusing a post partial or some other nice shortcuts, but I'll leave that as an exercise for you to figure out.
You need 2 models: User and Post. There is a relation between them: User HAS MANY posts, post BELONGS TO user. To create this relation in a database you should add user_id column to posts table. To do this simply run the following command:
rails generate migration AddUserIdToPosts user_id: integer
Don't forget to run rake db:migrate after that
To create association between models add to the User model:
has_many :posts, dependent: :destroy
And to Post model:
belongs_to :user
Now you can use 'user' method on post and 'posts' method on user. For example in show action of users controller:
#user = User.find(params[:id])
#posts = #user.posts
This links will help you:
http://guides.rubyonrails.org/association_basics.html
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
New updates
Moved params permit responsability from model to controller and used comment_attributes instead of comments as #vinodadhikary pointed me
Using better_errors REPL, I traced the problem down to sanitize_for_mass_assignment method. When doing attributes.permitted? it returns false. But doing attributes.permit(:article_id, :name, :email, :body) returns me exactly que entry parameters!:
>> attributes
=> {"name"=>"Commenter", "email"=>"commenter#mail.com", "body"=>"Here is the comment >> body!! :D"}
>> attributes.permit(:article_id, :name, :email, :body)
=> {"name"=>"Commenter", "email"=>"commenter#mail.com", "body"=>"Here is the comment body!! :D"}
>> attributes.permitted?
=> false
Context and code
Trying to get in touch with Rails 4, I encountered a problem with (I think) strong parameters use.
I have an Article class which can have many Comments. When creating a new comment doing:
#comment = #article.comments.build(params[:comment])
I get the following error (pointing this line):
ActiveModel::ForbiddenAttributesError at /articles/1/comments
The models are the following:
class Article < ActiveRecord::Base
validates_presence_of :title, :content
validates_uniqueness_of :title
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :comments
end
Comments:
class Comment < ActiveRecord::Base
belongs_to :article
validates_presence_of :article_id, :author, :body, :content
end
Article controller have this in the private section:
def article_params
params.require(:article).permit(:title, :content, comments_attributes: [:article_id, :name, :email, :body])
end
Comments controller code is:
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.build(params[:comment]) # <--- It fails here
respond_to do |format|
if #comment.save
format.html { redirect_to #comment, notice: 'Comment was successfully created.' }
format.json { render action: 'show', status: :created, location: #comment }
else
format.html { render action: 'new' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
The methods article_params and comment_params that you have in the models belong in their respective controllers not in models. The idea is to filter the parameters passed to the model in the controller rather than in the model. Take a read on http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html, on how to allow attributes for nested attributes.
You models should be as follows:
# Articles.rb
class Article < ActiveRecord::Base
validates_presence_of :title, :content
validates_uniqueness_of :title
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :comments
end
# Comment.rb
class Comment < ActiveRecord::Base
belongs_to :article
validates_presence_of :article_id, :author, :body, :content
end
Then move the strong parameters to Articles Controller as follows:
#ArticlesController.rb
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to #comment, notice: 'Comment was successfully created.' }
format.json { render action: 'show', status: :created, location: #comment }
else
format.html { render action: 'new' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
private
def article_params
params.require(:article).permit(:title, :content, comments_attributes: [:article_id, :author, :email, :body, :content])
end
permit params method name should be same as model/controller
e.g if model name is "recent_post" then permit method name should be
def recent_post_params
..............
end
I have a page that has a list of referrals on it. I have a button on each of the referrals that is set to reply to the referral. I don't need any pop up or form to show except for a flash message to show the user has successfully replied to the referral and toggling a class on the button when a user replies. Upon replying to the referral, email(is index for the table) is passed, referralid is also passed to the reply table. I have tried many methods, but I'm getting nowhere with the controllers. I created proper associations on the models, but still getting nowhere in the controller logic to create a reply record for every reply. Here are my models:
Referral Model
class Referral < ActiveRecord::Base
attr_accessible :referraltype
belongs_to :user
validates :user_id, presence: true
has_many :replies
def nil_zero?
self.nil? || self == 0
end
end
User Model
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :provider, :uid, :image
has_attached_file :image, styles: { medium: "320x320>", thumb: "50x50" }
has_many :referrals
has_many :replies
end
Replies Controller
class RepliesController < ApplicationController
end
Reply Model
class Reply < ActiveRecord::Base
belongs_to :user
belongs_to :referral
end
Referrals Controller
class ReferralsController < ApplicationController
before_filter :authenticate_user!
def reply_to_referral
#referral = Referral.find(params[:referral_id])
#replier_id = params[:replier_id]
#reply = #referral.replies.create(replier_id: #replier_id)
flash[:success] = "Referral reply sent."
redirect_to root_path
end
# GET /referrals
# GET /referrals.json
def index
#referrals = Referral.order("created_at desc")
#referrals
respond_to do |format|
format.html # index.html.erb
format.json { render json: #referrals }
end
end
# GET /referrals/1
# GET /referrals/1.json
def show
#referral = Referral.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #referral }
end
end
# GET /referrals/new
# GET /referrals/new.json
def new
#referral = current_user.referrals.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #referral }
end
end
# GET /referrals/1/edit
def edit
#referral = current_user.referrals.find(params[:id])
end
# POST /referrals
# POST /referrals.json
def create
#referral = current_user.referrals.new(params[:referral])
respond_to do |format|
if #referral.save
format.html { redirect_to #referral, notice: 'Referral was successfully created.' }
format.json { render json: #referral, status: :created, location: #referral }
else
format.html { render action: "new" }
format.json { render json: #referral.errors, status: :unprocessable_entity }
end
end
end
# PUT /referrals/1
# PUT /referrals/1.json
def update
#referral = current_user.referrals.find(params[:id])
respond_to do |format|
if #referral.update_attributes(params[:referral])
format.html { redirect_to #referral, notice: 'Referral was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #referral.errors, status: :unprocessable_entity }
end
end
end
# DELETE /referrals/1
# DELETE /referrals/1.json
def destroy
#referral = current_user.referrals.find(params[:id])
#referral.destroy
respond_to do |format|
format.html { redirect_to referrals_url }
format.json { head :no_content }
end
end
end
Routes.rb
GemPort::Application.routes.draw do
resources :referrals do
resources :replies
member do
put "reply_to_referral"
end
end
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
root :to => 'pages#home'
get 'about' => 'pages#about'
end
Migration for the Replies table
class CreateReplies < ActiveRecord::Migration
def change
create_table :replies do |t|
t.references :user
t.references :referral
t.timestamps
end
add_index :replies, :user_id
add_index :replies, :referral_id
end
end
code on the _referral.html.haml partial that is giving the error:
= link_to '<i class="icon-ok icon-large pull-right icon-grey" rel="tooltip" title="Reply"> Reply</i>'.html_safe, reply_to_referral_path(referral_id: referral.id, replier_id: current_user.id)
I know this must be simple to do in the controller, I tried using a helper but got nowhere
Add your routes and controller and we can give you a better answer, but I'm guessing that this isn't working since you're passing an email to the route.
Emails have full stops (.) which can break your route unless you add constraints to the route.
Try changing your route to something like:
resources :referrals do
member do
put "reply_to_referral" # will give you referrals/:id/reply_to_referral
end
end
Now change your link to reply_to_referral_path(id: referral.id, email: current_user.email), this should come out as /referrals/32/reply_to_referral?email=user#email.com
Then in referrals controller:
def reply_to_referral
#referral = Referral.find(params[:id])
#email = params[:email]
# now make sure your referral_replies table has a column called 'email' and
# also one called 'referral_id', then you can do:
#referral_reply = #referral.referral_replies.create(email: #email)
flash[:success] = "Referral reply sent."
redirect_to # wherever required
end
You could do something similar by adding a constraint to the route, or by passing in the user's id instead of email and then querying the database.
To style the button you can then check if the referral has any replies:
<% if referral.referral_replies.any? %>
# add a CSS class
<% end %>