Rails 4 Mass Assignment Whitelisting Parameters for Admin User - ruby-on-rails

I've been looking around and trying to see how I would handle mass-assignment with Rails 4. I know this question has been beaten to death, but from my search, I've only come across answers that require the protected_attributes gem and attr_accessible. Now I'm not sure if that is still the industry standard on this, so I wanted to ask.
I'm using Rails 4.0.1 and I am trying to figure out how I can limit specific parameter updates to admin accounts only.
Here are the parameters:
:title, :summary, :content, :status
Now when a user creates a post, they can only update the following attributes:
:title, :summary, :content
However, if an admin updates the post, they are able to update
:title, :summary, :content AND :status
post_controller.rb
def create
#post = Post.new(post_params)
#post.status = 'pending'
respond_to do |format|
if #post.save
PostMailer.new_post(#post).deliver
format.html { redirect_to #post, notice: 'Post was successfully submitted.' }
format.json { render action: 'show', status: :created, location: #post }
else
format.html { render action: 'new' }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
def update
#categories = Category.all
respond_to do |format|
#post.slug = nil
if params[:post][:featured]
#image = Imgurruby::Imgur.new('20e2a9ef8542b15873a0dfa7502df0b5')
#image.upload(params[:post][:featured])
params[:post][:featured] = #image.url.to_s
end
if #post.update(post_params)
expire_fragment(#post)
#post.friendly_id
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
def post_params
params.require(:post).permit(:title, :summary, :category, :tags, :content, :user_id, :category_id, :slug, :featured, :views)
end
Would I be right in assuming that the best way would be to use an operator in the post_params method to check if the user is an admin, and if so, permit a different set of parameters?

of course, you can use different sets of parameters for different users or for different actions. you would do something like:
def update
if user.is_a? Admin
#post.update(post_params_admin)
else
#post.update(post_params_user)
end
end
def post_params_user
params.require(:post).permit(:title, :summary, :category, :content)
end
def post_params_admin
params.require(:post).permit(:title, :summary, :category, :tags, :content, :user_id, :category_id, :slug, :featured, :views)
end

Related

param is missing or the value is empty: task. How can I make possible to add a "To Do" while also rendering the view to create a project?

I want that when the user creates a project s/he could also add new tasks into it. I have figured out this "solution" and I have encountred this error that I can't solve.
I dont even know of it is "ruby correct" that I mix tasks actions with the projects controller. The reason I took this approach is that I couldn't figure out for a solution for doing so in the views and make the project be saved..
This is my projects_controler.rb
class ProjectsController < ApplicationController
before_action :set_project, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
def create
#project = Project.new(project_params)
#task = Task.new(task_params)
respond_to do |format|
if #project.save and #task.save
format.html { redirect_to #project, notice: 'Project was successfully created.' }
format.json { render :show, status: :created, location: #project }
else
format.html { render :new }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /projects/1
# PATCH/PUT /projects/1.json
def update
respond_to do |format|
if #project.update(project_params)
format.html { redirect_to #project, notice: 'Project was successfully updated.' }
format.json { render :show, status: :ok, location: #project }
else
format.html { render :edit }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
# DELETE /projects/1
# DELETE /projects/1.json
def destroy
# #list = List.find(params[:id])
#project.destroy
respond_to do |format|
format.html { redirect_to projects_url, notice: 'Project was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_project
#project = Project.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def project_params
params.require(:project).permit(:title, :description, :done, :user_id)
end
def set_task
#task = Task.find(params[:id])
end
def task_params
params.require(:task).permit(:title, :done, :project_id)
end
end
project.rb
class Project < ApplicationRecord
belongs_to :user, foreign_key: "user_id"
has_many :tasks , dependent: :destroy
validates_presence_of :user
validates_uniqueness_of :title, on: :create, message: "must be unique"
validates :title, presence: true, length: { minimum: 2 }
def done_tasks
tasks.where(done: true).order("updated_at DESC")
end
end
task.rb
class Task < ApplicationRecord
belongs_to :project, foreign_key: "project_id"#, class_name: "List"
validates_presence_of :title
def completed?
!completed_at.blank?
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
resources :projects do
resources :tasks
end
authenticated :user do
root to: 'projects#index'
end
unauthenticated :user do
root 'welcome#home'
end
end
new.html.erb
<h1>New Project</h1>
<%= render 'form', project: #project, task: #task %>
<%# render 'form', project: #project, task: #task %>
<!-- custom -->
<!-- shall I ADD ID TO THE projrct t be trackable by the task????? and describe that in task.rb -->
<!-- <h1>Tasks</h1> -->
<%# f.button %>
<!-- button to add a new task -->
<%# f.button "add a new task to the current project" %>
<!-- if else mechanism for adding a new task via a button -->
<!-- when button pressed, tasks/_form will appear down here -->
<!-- button to save the task while at the same moment ..... override "create task" and "create project" to save "save project to task" -->
<%# render '../tasks/_form' %>
<%= link_to 'Back', projects_path %>
_form.html.erb
<%= simple_form_for(#project, #task) do |f| %>
<%# form_for([#post, #comment]) do |f| %>
<% f.error_notification %>
<div class="form-inputs">
<%= f.input :title %>
<%= f.input :description %>
<%= f.input :done %>
<%= f.association :user %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
In task_params
params.require(:task).permit(:title, :done)
and create method should be like
def create
#project = Project.new(project_params)
#project.tasks.build(task_params)
respond_to do |format|
if #project.save and #task.save
format.html { redirect_to #project, notice: 'Project was successfully created.' }
format.json { render :show, status: :created, location: #project }
else
format.html { render :new }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end

Adding single item to rails has_many :through

I have a has_many :thorugh relationship between customers and software products they own.
# company.rb
class Company < ActiveRecord::Base
has_many :company_sources
has_many :sources, :through => :company_sources
end
# source.rb
class Source < ActiveRecord::Base
has_many :company_sources
has_many :companies, :through => :company_sources
end
# company_source.rb
class CompanySource < ActiveRecord::Base
belongs_to :company
belongs_to :source
end
The controllers are the default rails g scaffold <name> files
I need a selection form on the company edit page that will allow the addition of a single source to the company_source table.
The closest I can get is using the selection form helper, however that will overwrite the previous addition when I go to add a new item.
I've been playing with this for quite a few hours now and I can't seem to get the form/routes/controller right.
This is the form I'm playing with at the time of writing
<table>
<% #company.sources.each do |source| %>
<tr><%= source.name %></tr>
<% end %>
<tr>
<%= form_for #company do |f| %>
<td>
<%= select("source", "id", Source.all.collect {|p| [ p.name, p.id ]}, { include_blank: true })%>
</td>
<td>
<%= f.submit "Add Source" %>
</td>
<% end %>
</tr>
</table>
Full controller (again, at time of writing)
class CompaniesController < ApplicationController
before_action :set_company, only: [:show, :edit, :update, :destroy]
# GET /companies
# GET /companies.json
def index
#companies = Company.all
end
# GET /companies/1
# GET /companies/1.json
def show
end
# GET /companies/new
def new
#company = Company.new
end
# GET /companies/1/edit
def edit
end
# POST /companies
# POST /companies.json
def create
#company = Company.new(company_params)
respond_to do |format|
if #company.save
format.html { redirect_to #company, notice: 'Company was successfully created.' }
format.json { render :show, status: :created, location: #company }
else
format.html { render :new }
format.json { render json: #company.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /companies/1
# PATCH/PUT /companies/1.json
def update
respond_to do |format|
if #company.update(company_params)
format.html { redirect_to #company, notice: 'Company was successfully updated.' }
format.json { render :show, status: :ok, location: #company }
else
format.html { render :edit }
format.json { render json: #company.errors, status: :unprocessable_entity }
end
if (params[:source_id])
#company.source << Source.find(params[:source_id])
end
end
end
# DELETE /companies/1
# DELETE /companies/1.json
def destroy
#company.destroy
respond_to do |format|
format.html { redirect_to companies_url, notice: 'Company was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_company
#company = Company.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def company_params
params.require(:company).permit(:name, :description, :source_id)
end
end
In the update action, I propose not doing #company.update, and instead doing:
company_source = CompanySource.create!(company: #company, source: Source.find(source_id)
(Warning: There might be errors in the code, which you should be able to correct fairly easily)
So, the update action would look like:
def update
respond_to do |format|
company_source = CompanySource.new(company: #company, source_id: params[:source_id])
if company_source.save
format.html { redirect_to #company, notice: 'Company was successfully updated.' }
format.json { render :show, status: :ok, location: #company }
else
format.html { render :edit }
format.json { render json: company_source.errors, status: :unprocessable_entity }
end
end
end
Although this shifts the perspective to that of the CompanySource despite being inside the CompaniesController, what you are really wanting to do is create a new CompanySource. I think this is the most straightforward way of looking at it.
This will ensure correct updates of the Compnay-Source relationship.

Unpermitted parameter using fields_for Rails

I got such message in console when trying to use fields_for in Rails:
Parameters: .... "task"=>{"task_name"=>"111", "tag"=>{"tag_text"=>"222"}}, "commit"=>"Save"}
Unpermitted parameter: tag
My models with has_many and belongs_to:
class Task < ActiveRecord::Base
has_many :tags
accepts_nested_attributes_for :tags
end
class Tag < ActiveRecord::Base
belongs_to :task
end
Form helper for new/edit page:
<%= form_for(#task) do |f| %>
<%= f.text_field :task_name %>
<%= f.fields_for([#task, #task.tags.build]) do |t| %>
<%= t.text_field :tag_text %>
<% end %>
<%= f.submit 'Save' %>
<% end %>
My Task Controller (I used scaffold to generate it, and its mostly default)
class TasksController < ApplicationController
before_action :set_task, only: [:show, :edit, :update, :destroy]
def index
#tasks = Task.all
end
def show
end
def new
#task = Task.new
#task.tags.build
end
def edit
#task.tags.build
end
def create
#task = Task.new(task_params)
respond_to do |format|
if #task.save
format.html { redirect_to #task, notice: 'Task was successfully created.' }
format.json { render :edit, status: :created, location: #task }
else
format.html { render :new }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #task.update(task_params)
format.html { redirect_to edit_task_path, notice: 'Task was successfully updated.' }
format.json { render :edit, status: :ok, location: #task }
else
format.html { render :edit }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end
def destroy
#task.destroy
respond_to do |format|
format.html { redirect_to tasks_url, notice: 'Task was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_task
#task = Task.find(params[:id])
end
def task_params
params.require(:task).permit(:task_name, :task_sum, :status_id, :user_id, :target_release_id, tag_attributes: [:tag_text, :task_id])
end
end
I think the problem is in the way you use fields_for. Try to make this way:
<%= f.fields_for :tags do |t| %>
That way the param tags_attributes will be send and every thing should be fine.
EDIT
Also, if you are using Rails 5, you need to set the belongs_to as optional so the accepts_nested_attributes_for can work properly. So:
class Tag < ActiveRecord::Base
belongs_to :task, optional: true
end
And the parameters sanitization in your tasks_controller.rb you do not need tag_id under tags_attributes:
def task_params:
params.require(:task).permit(:task_name, tags_attributes: [:tag_text])
end
EDIT
I think I found your problem. Try to put tags_attributes instead of tag_attributes in your task_paramsmethod.

Inserting data into multiple tables using one form

i am new to ruby on rails...
i'm making a project on office space rent
each listing has many images
there's a listing table and images table
class Listing < ActiveRecord::Base
attr_accessible :agent_id, :description, :key_feature, :location_id, :size, :title, :office_type, :parking_ratio, :floors, :price, :nearest_metro, :distance, :image, :location_title, :display_order, :images_attributes
has_many:images, :dependent => :destroy
accepts_nested_attributes_for :image
end
class Image < ActiveRecord::Base
# attr_accessible :title, :body
attr_accessible :id, :image_url, :title_image, :listing_id, :created_at, :updated_at
belongs_to :listing
end
I want to make form for listings so that it also asks for adding images for that particular listing.It should make a blank tab for image_url and upload small image of it to its right.And below it should ask for add new image... clicking that renders another image_url tab and so on...
Listing should be saved in listings table and images should be saved in images table refering that listing..Please help me!!!
I've made form using following code :-
<%=f.fields_for :images do |image| %>
<%= image.label :image %><br />
<%= image.text_field :image_url %><br /> <br />
<%= image.hidden_field :listing_id, :value => params[:id] %>
It would show images already manually inserted in DB for listings and thereby, showing them when editing a particular listing.But when asking for making a new listing,it never shows image tab... So m helpless..I suspect whole structure might be wrong.. Please help me!
And here's my listing controller for update and create
def create
#listing = Listing.new(params[:listing])
respond_to do |format|
if #listing.save
format.html { redirect_to ([:member, #listing]), notice: 'Listing was successfully created.' }
format.json { render json: #listing, status: :created, location: #listing }
else
format.html { render action: "new" }
format.json { render json: #listing.errors, status: :unprocessable_entity }
end
end
end
def update
#listing = Listing.find(params[:id])
respond_to do |format|
if (#listing.update_attributes(params[:listing]))
format.html { redirect_to ([:member, #listing]), notice: 'Listing was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #listing.errors, status: :unprocessable_entity }
end
end
end

form_for tag and nested form - to use custom method from controller

I'm new to Rails and making application where college members (teachers and students) can create posts and comment on them. Later on I wish to add nesting (ancestry) and points system in it.
I have Post, Comment and Member model. The Post model was made via Scaffolding, Member model was made with help of Devise, and Comment is just a model.
In my show page of Post, I'd like to have comments beneath the posts, I've made some progress (thanks to SO I came to know quite a bit) but now I am stuck with a problem that whenever I attempt to post a blank comment, rails was redirecting to the edit page. How to change this so that rails stays only on the show page and display errors?
For this I searched a bit, created a new method 'update_comments' in post_controller.rb and tried modifying the forms_for tag attributes, as in the code below, but now I get routing error on submitting.
app/models/member.rb
class Member < ActiveRecord::Base
#Associations
belongs_to :department
has_one :student, :dependent => :destroy
accepts_nested_attributes_for :student
has_one :nstudent, :dependent => :destroy
accepts_nested_attributes_for :nstudent
has_many :posts, :dependent => :destroy
has_many :comments, :dependent => :destroy
end
app/models/post.rb
class Post < ActiveRecord::Base
#Associations
belongs_to :member
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :comments
end
app/models/comment.rb
class Comment < ActiveRecord::Base
# Associations
belongs_to :member
belongs_to :post
validates_presence_of :content
end
config/routes.rb
Urdxxx::Application.routes.draw do
devise_for :members
resources :posts do
member do
get 'update_comment'
end
end
root :to => 'posts#index'
app/controllers/posts_controller.rb
class PostsController < ApplicationController
# Devise filter that checks for an authenticated member
before_filter :authenticate_member!
# GET /posts
# GET /posts.json
def index
#posts = Post.find(:all, :order => 'points DESC')
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
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.member_id = current_member.id if #post.member_id.nil?
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
# Not made by scaffold
def update_comment
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to #post, notice: 'Comment was successfully created.' }
format.json { head :no_content }
else
format.html { render action: "show" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
end
app/views/posts/show.html.erb
<p> Have your say </p>
<%= form_for #post, :url => {:action => 'update_comment'} do |p| %>
<%= p.fields_for :comments do |c| %>
<!-- Following 3 lines saved my life -->
<% if c.object.new_record? %>
<%= c.text_area :content, :rows => 4 %>
<%= c.hidden_field :member_id, value: current_member.id %>
<% end %>
<% end %>
<%= p.submit "Reply" %>
<% end %>
image of my show page:
http://i.stack.imgur.com/TBgKy.png
on making a comment:
http://i.stack.imgur.com/JlWeR.png
Update:
Looked back and made changes here, following what Ken said. I don't know how but it works for now.
app/controllers/posts_controller.rb
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 }
elsif :comments
format.html { render action: "show" }
format.json { render json: #post.errors, status: :unprocessable_entity }
else
format.html { render action: "edit" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
You don't need a custom method. It is not very RESTful. See, e.g., http://www.sitepoint.com/restful-rails-part-i/ for info on REST. This is not a case where there is justification to use a custom method.
Whenever you find yourself adding custom methods you should think long and hard about whether it's necessary. Usually if you need custom methods what you actually need is another controller (or a different set of controllers).
The update method here is all you need. If you really want to go to the show method after a failed update (though I don't know why) then change the render edit call in the block in the update method after the update fails.
It seems like your real problem is the edit view isn't showing errors. Although the scaffold generated view should do that so maybe you changed it.
In case you missed it you may also benefit from this screencast:
http://railscasts.com/episodes/196-nested-model-form-part-1
You need to update the method type in route and also needs to sets the form post method to your new action, also when you submit a form its an post request not a get request.
Urdxxx::Application.routes.draw do
devise_for :members
resources :posts do
collection do
post :update_comment
end
end
root :to => 'posts#index'
<p> Have your say </p>
<%= form_for :post, :url => {:action => 'update_comment'} do |p| %>
<%= p.fields_for :comments do |c| %>
<!-- Following 3 lines saved my life -->
<% if c.object.new_record? %>
<%= c.text_area :content, :rows => 4 %>
<%= c.hidden_field :member_id, value: current_member.id %>
<% end %>
<% end %>
<%= p.submit "Reply" %>
<% end %>

Resources