inherited_resources alternate create method forbiddenattributes - ruby-on-rails-4

I have two create methods (maybe could be structured better, but new to inherited_resources)
Basically, I want to redirect to a different page after create, I am getting a ForbiddenAttributes error using one method, but not the original Create action, I'm guessing there is some special way to use IH, but I am stumped on this one.
In my second action, I need to manually assign the params - I'm guessing I need to do this the IH way, that line is where it blows up so the question is how is IH achieving this without an error?
def create
if can? :create, LeaveRequest
create! { leave_requests_url }
end
end
def manage_create
#leave_request = LeaveRequest.new(params[:leave_request])
if can? :create, LeaveRequest
create! { manage_leave_requests_url }
end
end
def permitted_params
{:leave_request => params.fetch(:leave_request, {}).permit(:user_id, :controller, :manager_id, :part_day, :comment, :selected_dates, :status, :leave_type_id, leave_dates_attributes:
[:id, :leave_request_id, :hours, :date_requested, :_destroy])}
end
Route is defined as
match 'manage_create', to: 'leave_requests#manage_create', as: :manage_create_leave_request, via: [:post]
I'm using IH 1.4.1

I needed to add the full list of parameters to my manage_create function - I have absolutely no idea why it doesn't use the existing permitted_params method.
#leave_request = LeaveRequest.new(params[:leave_request].permit(:user_id, :controller, :manager_id, :part_day, :comment, :selected_dates, :status, :leave_type_id, leave_dates_attributes:
[:id, :leave_request_id, :hours, :date_requested, :_destroy]))

Related

Assign nested attributes records to current user when using Cocoon gem

In my application I have models Post & Slides & I have:
class Post < ActiveRecord::Base
has_many :slides, inverse_of: :post
accepts_nested_attributes_for :slides, reject_if: :all_blank, allow_destroy: true
Everything works fine, only thing I need (because of how my application will work), is when a slide is created, I need to assign it to current_user or user that is creating the record.
I already have user_id in my slides table and:
class User < ActiveRecord::Base
has_many :posts
has_many :slide
end
class Slide < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
My PostsController looks like this:
def new
#post = current_user.posts.build
// This is for adding a slide without user needing to click on link_to_add_association when they enter new page/action
#post.slides.build
end
def create
#post = current_user.posts.build(post_params)
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Was successfully created.' }
else
format.html { render :new }
end
end
end
Any help is appreciated!
There are two ways to accomplish this:
First option: when saving the slide, fill in the user-id, but this will get pretty messy quickly. You either do it in the model in a before_save, but how do you know the current-user-id? Or do it in the controller and change the user-id if not set before saving/after saving.
There is, however, an easier option :) Using the :wrap_object option of the link_to_add_association (see doc) you can prefill the user_id in the form! So something like:
= link_to_add_association ('add slide', #form_obj, :slides,
wrap_object: Proc.new {|slide| slide.user_id = current_user.id; slide })
To be completely correct, you would also have to change your new method as follows
#post.slides.build(user_id: current_user.id)
Then of course, we have to add the user_id to the form, as a hidden field, so it is sent back to the controller, and do not forget to fix your strong parameters clause to allow setting the user_id as well :)
When I'm looking at this I see three ways to go about it, but since you're on cocoon already, I would drop the connection between user & slides - as it kind of violates good database practices (until you hit a point where you page is so popular you have to optimize of course, but that would be done differently).
You are using cocoon, but you're not utilizing the nesting of the relationship fully yet ...
The best practice would be to have cocoon's nesting create both & instead of trying to assign to current_user you call something like:
#slides = current_user.posts.find_first(param[:id]).slides
The #slides saves all the results, the .Post.find(param[:id]) finds a specific post for current_user.
Note: this is not the most optimized way & I haven't tested this, but it shows you the format of one way you can think about the relationships. You will need to hit rails console and run some tests like ...
(rails console)> #user = User.first
Next we test that there are posts available, as it's frustrating to test blanks & not get the results ...
(rails console)> #posts = #user.posts
Then we use the find method & I'm going to use Post.first just to get a working id, you can easily put "1" or any number you know is valid ...
(rails console)> #post = #posts.find(Post.first)
Finally, we go with either all slides to make sure its a valid dataset
(rails console)> #post.slides
If you want a specific slide later & have a has_many relationship just tag that find method on the .slides after.
Also one last thing - when you state earlier in there you need the current_user to be related, you can use an entry in your model.rb to create a method or a scope to get the data & allow you to link it to the current_user more easily & even drop some directed SQL query with the .where method to pull that information up if performance is an issue.
I spotted a second optimization in there ... if everything really is working - don't worry about this!
And don't forget about the strong_parameters nesting to do this fully ... Strong Param white listing
Basic format ... `.permit(:id, :something, slide_attributes: [:id, :name, :whatever, :_destroy])

Rails 4 - Column exists but does not update

I have recently added a field "tag" to my blog app built in Rails 4. Below you can see the field appearing in the Edit view:
But once I return to the Show view after editing, this does not appear:
When I check the database directly I can definitely see it exists:
sqlite> PRAGMA table_info(POSTS);
0|id|INTEGER|1||1
1|title|varchar(255)|0||0
2|body|text|0||0
3|created_at|datetime|0||0
4|updated_at|datetime|0||0
5|slug|varchar(255)|0||0
6|tag|varchar(255)|0||0
Can anyone suggest what is going on or how to troubleshoot this?
Rails 4 uses strong parameters by default. This means you have to explicitly whitelist params you wish to mass assign.
When adding a new attribute to a model, you have to remember to update the permitted params in you controller.
For example, in your case, you would need to make sure :tags are added like so:
class PostController < ActionController::Base
def update
post = Post.find(params[:id])
post.update(post_params)
redirect_to post
end
private
def post_params
params.require(:post).permit(:title, :body, :tag)
end
end

ActiveModel::ForbiddenAttributesError + cancan + rails 4 + model with scoped controller

I m using cancan(1.6.10) with rails 4.0.0. I have a model called 'App'(not scoped) and a controller Admin::AppsController(its scoped. ie app/controllers/admin/apps_controller).
the controller code is as
class Admin::AppsController < ApplicationController
before_filter :authenticate_user!
load_and_authorize_resource class: App
def index
end
#CRUD methods and some other custom methods
...
private
def app_params
params.require(:app).permit(:name, :description, :author, :url_path, :validated, :active, :version)
end
end
I m getting error when i try to create a 'app'.
ActiveModel::ForbiddenAttributesError - ActiveModel::ForbiddenAttributesError:
activemodel (4.0.0) lib/active_model/forbidden_attributes_protection.rb:21:in `sanitize_for_mass_assignment'
I added
before_filter do
resource = controller_path.singularize.gsub('/', '_').to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
as specified in https://github.com/ryanb/cancan/issues/835#issuecomment-18663815 but still getting the above error.
Using name spaces. Please try to change your code to this one below. I had same issue after #JiriKolarik suggested his solution to work with name spaces. I hope it helps.
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
if you use this workflow
before_filter do
resource = controller_path.singularize.gsub('/', '_').to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
then your params method should look like this
def admin_app_params
params.require(:admin_app).permit(:name, :description, :author, :url_path, :validated, :active, :version)
end
The reason why, it's because form generators (form_form, simple_form) generate params with namespace_resource
So if you have Blog::Post, form generator will create params like this
{ "blog_post"=>{"title"=>"Post"}, "commit"=>"Create", "action"=>"create", "controller"=>"blog/posts", "locale"=>"en"}
And this is how before filter works:
before_filter do
resource = controller_path.singularize.gsub('/', '_').to_sym # => 'blog/posts' => 'blog/post' => 'blog_post' => :blog_post
method = "#{resource}_params" # => 'blog_post_params'
params[resource] &&= send(method) if respond_to?(method, true) # => params[:blog_post]
end
If you need read :blog_post from params, solution above will not work. If you need read :post from params, then this solution will not work, if your controller will be blog/post
cancan just does not work with the strong parameter. While there is a new gem cancancan which works well without any code change.

Does ActiveModel::Serializer require an explicit render call?

I know that when using view templates (html, rabl), I don't need an explicit render call in my controller action because by default, Rails renders the template with the name corresponding to the controller action name. I like this concept (not caring about rendering in my controller code) and therefore wonder whether this is possible as well when using ActiveModel::Serializers?
Example, this is code from a generated controller (Rails 4.1.0):
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
#other actions
# GET /products/1
# GET /products/1.json
def show
end
end
and this is the serializer:
class ProductSerializer < ActiveModel::Serializer
attributes :id, :name, :description, :url, :quantity, :price
end
Hitting /products/1.json, I would expect two things to happen:
Fields not listed in the serializer to be ommited,
Whole JSON object to be incapsulated within a 'product' top level field.
However, this does not happen, whole serializer is ignored. But then if I modify the Show method to the following:
# GET /products/1
# GET /products/1.json
def show
#product = Product.find(params[:id])
respond_to do |format|
format.html
format.json { render json: #product }
end
end
And now it is all fine, but I have lost the benefit of the before_action filter (and it seems to me that I have some redundant code).
How should this really be done?
Without an explicit render or respond_with or respond_to Rails will look for a matching template. If that template does not exist Rails throws an error.
However, you can create your own resolver to bypass this. For instance, suppose you created app\models\serialize_resolver.rb and put this into it:
class SerializeResolver < ActionView::Resolver
protected
def find_templates(name, prefix, partial, details)
if details[:formats].to_a.include?(:json) && prefix !~ /layout/
instance = prefix.to_s.singularize
source = "<%= ##{instance}.active_model_serializer.new(##{instance}).to_json.html_safe %>"
identifier = "SerializeResolver - #{prefix} - #{name}"
handler = ActionView::Template.registered_template_handler(:erb)
details = {
format: Mime[:json],
updated_at: Date.today,
virtual_path: "/#{normalize_path(name, prefix)}"
}
[ActionView::Template.new(source, identifier, handler, details)]
else
[]
end
end
def normalize_path(name, prefix)
prefix.present? ? "#{prefix}/#{name}" : name
end
end
And then, in either your application controller (or in an individual controller) place:
append_view_path ::SerializeResolver.new
With that you should be able to do what you want. If it is a json request, it will create an erb template with the right content and return it.
Limitations:
This is a bit clunky because it relies on erb, which is not needed. If I have time I will create a simple template handler. Then we can invoke that without erb.
This does wipe out the default json response.
It relies on the controller name to find the instance variable (/posts is converted to #post.)
I've only tested this a little. The logic could probably be smarter.
Notes:
If a template is present, it will be used first. That allows you to override this behavior.
You can't simply create a new renderer and register it, because the default process doesn't hit it. If the template is not found, you get an error. If the file is found, it goes straight to invoking the template handler.
The 'redundant code' we see in the second one, is this line only:
#product = Product.find(params[:id])
And I believe this is the same logic as your before_action. You don't need this line, just remove it. Now the duplication is removed.
To the remaining part. An action needs to know what to render. By default if the action is empty or absent, the corresponding 'action_name'.html.erb (and other formats specified by respond_to) will be looked up and rendered.
This is why what the Rails 4 generator created works: it creates the show.html.erb and show.json.jbuilder which get rendered.
With ActiveModel::Serializer, you don't have a template. If you leave the action empty, it doesn't have a clue what to render. Thus you need to tell it to render the #product as json, by either:
render json: #product
or
respond_with #product

Strong parameters for nested attributes returns "unpermitted parameters" when empty array

Assuming a User model
using Rails4 with strong_parameters.
class User < ActiveRecord::Base
has_secure_password
accepts_nested_attributes_for :identity
// rest of code omitted for brevity
end
If I refer to the guide I should be able to do
def user_params
params.require(:user).permit(:email, identity_attributes: [])
end
to allow mass_assignment of each identity_attributes whatever their names or number. But this run in a "Unpermitted parameters: identity_attributes"
But if I specify the identity_attributes it works
def user_params
params.require(:user).permit(:email, identity_attributes: [:last_name, :first_name])
end
I have many attributes in Identity, I would be able to mass_assign them through User without specifying all of them.
Am I missing something ? Is it a bug ?
Cheers
You have to specify the identity's attributes you want to updated, including the :id of the identity entity.
you will have something like that :
def user_params
params.require(:user).permit(:email, identity_attributes: [:id, :last_name, :first_name])
end
if you don't specify the :id, Rails will try to create an entity instead of updating it. I spend all the week-end struggling on a simple one-to-many relationship using accepts_nested_attributes_for because I didn't specified the id in the permitted attributes.