I have a field in my model called isTransfer:
class AddTxfrColumnsToTransaction < ActiveRecord::Migration
def change
add_column :transactions, :isTransfer, :boolean
add_column :transactions, :transferAccount_id, :integer
end
end
I create a controller that should act like action: :new, but only for a transfer call new_transfer:
def new_transfer
account = Account.find(params[:account_id])
#transaction = account.transactions.build
#transaction.description = "Transfer"
#transaction.isTransfer = true
#transaction.amount = 100
respond_to do |format|
format.html # new.html.erb
format.json { render json: #transaction }
end
end
When I see the new transfer in my view form, before posting, I can see that isTransfer is set to true. But when I post, it always goes into the DB as false. The other fields (description and amount) do not change - they go in as expected.
Here is the model:
class Transaction < ActiveRecord::Base
attr_accessible :account_id, :amount, :check, :date, :description, :is_cleared, :note, :category, :isTransfer, :transferAccount_id
validates_presence_of :amount, :date
belongs_to :account, class_name: 'Account'
belongs_to :transferAccount, class_name: 'Account'
end
I would suggest you do the presets in the create controller method rather than new
Also, you can add a "!" to make the save method return you any errors from the console: e.g
def create
###do your preset methods here
if(#transaction.save!)
end
end
Ok, this is probably a complete noob mistake. I had originally believed that if I set a value in the controller (as part of new_transfer action) that it would persist to the create action after submit. My mistake was that in not referencing it at all on the new_transfer form, it was never passed back to the Create action as a param. By adding the following to my new_transfer form, isTransfer now updates in the create action:
<%= f.hidden_field(:isTransfer) %>
Related
I'm building an application where I have used nested attributes to store different option records under a question record. There is a form where the user can dynamically add and remove options. Everything works fine in my create action, but in the update action, if I remove an existing option and submit the form, it is not deleted from the database.
When updating the question record, is there any way to completely overwrite the existing nested parameters and replace it with the ones we pass in the update request?
I understand that adding _destroy to the attributes and passing it as a parameter would satisfy my requirement here. Since I'm deleting the option information from my frontend state on press of a "remove" button in the UI, I'm not sending it along with the params. Is there any other method in Rails to completely overwrite nested attributes and delete those nested records which are not passed in the update request, from the update action in the controller itself?
question.rb
class Question < ApplicationRecord
belongs_to :quiz
has_many :options
validates :body, presence: true
accepts_nested_attributes_for :options
end
option.rb
class Option < ApplicationRecord
belongs_to :question
validates :body, presence: true
validates :is_correct, inclusion: { in: [ true, false ], message: "must be true or false" }
end
questions_controller.rb
class QuestionsController < ApplicationController
...
def update
#question = Question.find_by(id: params[:id])
if #question.update(question_params)
render status: :ok, json: { notice: t("question.successfully_updated") }
else
render status: :unprocessable_entity, json: { error: #question.errors.full_messages.to_sentence }
end
end
...
private
def question_params
params.require(:question).permit(:body, :quiz_id, options_attributes: [:id, :body, :is_correct])
end
Relevant question
If I understand you correctly you're deleting the options one by one by clicking a button next to the option. Thats not actually something you need or want to use nested attributes for. Nested attributes is only relevant when you're creating/editing multiple records at once.
While you can destroy a single nested record by updating the parent:
patch '/questions/1', params: {
question: { options_attributes: [{ id: 1, _destroy: true }] }
}
Its very clunky and not really a good RESTful design.
Instead you can just setup a standard destroy action:
# config/routes.rb
resources :options, only: :destroy
<%= button_to 'Destroy option', option, method: :delete %>
class OptionsController < ApplicationController
# #todo authenticate the user and
# authorize that they should be allowed to destroy the option
# DELETE /options/1
def destroy
#option = Option.find(params[:id])
#option.destroy
respond_to do |format|
format.html { redirect_to #option.question, notice: 'Option destroyed' }
format.json { head :no_content }
end
end
end
This uses the correct HTTP verb (DELETE instead of PATCH) and clearly conveys what you're doing.
I can share my recent project work which is a bit similar to your where I am using shrine gem for upload images and I can update/destroy images which is associated with a Product model
product.rb
.
.
has_many :product_images, dependent: :destroy
accepts_nested_attributes_for :product_images, allow_destroy: true
product_image.rb
.
belongs_to :product
.
_form.html.erb for update
<%= f.hidden_field(:id, value: f.object.id) %>
<%= image_tag f.object.image_url unless f.object.image_url.nil? %>
<%= f.check_box :_destroy %>
and in products controller,I have whitelisted this
product_images_attributes: [:_destroy,:image, :id]
Hope this helps you to solve on your case
I have an app that has a blog feature. Originally when I set this up a post post consisted of a title, body, keywords, and image link. I want to be able to filter posts based on keywords and I think the cleanest way to do this is to move keyword to their own table and associate the two tables. So each posts can have multiple keywords and each keyword can have multiple posts.
I created a migrations and model for keywords.
class CreateKeywords < ActiveRecord::Migration[6.1]
def change
create_table :keywords do |t|
t.string :keyword
t.timestamps
end
end
end
class Keyword < ApplicationRecord
has_and_belongs_to_many :posts
end
I associated that with the posts table and changed the posts model.
class CreatePostsKeywordsJoinTable < ActiveRecord::Migration[6.1]
def change
create_join_table :posts, :keywords do |t|
t.index [:post_id, :keyword_id]
t.index [:keyword_id, :post_id]
end
end
end
class Post < ApplicationRecord
belongs_to :user
has_and_belongs_to_many :keywords
validates :title, presence: true
validates :body, presence: true
accepts_nested_attributes_for :keywords
# validates :keywords, presence: true
end
For now I just commented out the keywords in the Post model. I'm not exactly sure if I need to remove it or not. I already have existing posts that I don't want to lose as part of this switchover so I'm trying to keep that I mind as I figure out how to make this work. Where I'm really confused is what I need to change in the controller.
This is my Post Controller:
require 'pry'
class Api::V1::PostController < ApiController
before_action :authorize_user, except: [:index, :show]
# INDEX /post
def index
render json: Post.all, each_serializer: PostSerializer
end
# SHOW /post/1
def show
render json: Post.find(params[:id]), serializer: PostShowSerializer
end
# CREATE /post/new
def create
binding.pry
post = Post.new(post_params)
post.user = current_user
if post.save
render json: post
else
render json: { errors: post.errors.full_messages }
end
end
# UPDATE /post/update
def update
post = Post.find(params[:id])
if post.update(post_params)
render json: post
else
render json: { errors: post.errors.full_messages }
end
end
# DESTROY /post/destroy
def destroy
post = Post.find(params[:id])
if post.destroy
render json: {destroyed: true}
end
end
protected
def post_params
params.require(:post).permit([:title, :body, :image, :keywords])
end
def authorize_user
if !user_signed_in? || current_user.role != "admin"
redirect_to root_path
end
end
end
In the above state when I get to this part post = Post.new(post_params) I get an error saying NoMethodError (undefined method 'each' for "authorlife":String). If I remove keywords from the post_params I get this error Unpermitted parameter: :keywords
I feel like I am missing one or more steps here but it's been awhile since I've done anything with associated tables like this.
UPDATE:
Followed some of the advice below and I updated the above code to how it currently looks. Current issue is that when I check post_parms in the #create method I'm no longer receiving keywords at all. I checked the frontend and it's sending keywords. I'm assuming it's my post_params that's causing the problem. I've tried adding the keywords nested attribute like this but keywords still isn't showing up in the post_params
def post_params
params.require(:post).permit(:title, :body, :image, :keywords_attributes => [:id, :keyword])
end
This is the WIP for the code I'm trying to implement. I'm not sure what the keywords part is supposed to look like once I get the params situation figured out.
params = { post: {title: post_params["title"], body: post_params["body"], image: post_params["image"], keywords_attributes: [{ keyword: 'keyword title' },]
}}
In the above state when I get to this part post = Post.new(post_params) I get an error saying NoMethodError (undefined method 'each' for "authorlife":String). If I remove keywords from the post_params I get this error Unpermitted parameter: :keywords
You need to setup nested attributes for keywords if you want to update them through a post.
class Post < ApplicationRecord
belongs_to :user
has_and_belongs_to_many :keywords
validates :title, presence: true
validates :body, presence: true
accepts_nested_attributes_for :keywords
end
You can then pass in params structured like this in your controller
params = { post: {
title: 'title', body: "body", keywords_attributes: [
{ text: 'keyword title' },
]
}}
post = Post.create(params[:post])
https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
I already have existing posts that I don't want to lose as part of this switchover so I'm trying to keep that I mind as I figure out how to make this work.
It's good practice to remove this not used data anymore. You should write a data migration which moves the existing keywords from the posts table to the keywords table. Something like this
class KeywordsMigrator
def run
Post.all.each do |post|
keyword = Keyword.find_or_create_by(title: post.keyword)
post.keywords << keyword
end
end
end
Finally you can drop the keyword column from post.
You haven't really mentioned the current structure of the posts table so I assume you have a keyword column there. If you have a keywords column you have to name your association different until you remove the column otherwise you will run into troubles. For example you rename it to keywords_v1 and specify the class_name.
class Post < ApplicationRecord
belongs_to :user
has_and_belongs_to_many :keywords_v1, class_name: "Keyword"
validates :title, presence: true
validates :body, presence: true
end
Or you rename the column first to something like deprecated_keywords.
I'm (very) new to ror and have read many tutorials for this issue but none seem to work. I'm trying to let one user create one booth to sell things.
This is my db migration:
class CreateBooths < ActiveRecord::Migration
def change
create_table :booths do |t|
t.string :name
t.references :user, index: true
t.timestamps null: false
end
add_index :booths, [:user_id]
end
end
Here is the booth controller:
class BoothsController < ApplicationController
before_action :logged_in_user
def new
#booth = Booth.new
end
def create
#booth = current_user.booths.build(booth_params)
if #booth.save
flash[:success] = "Congrats on opening your booth!"
redirect_to root_url
else
render 'new'
end
end
private
def booth_params
params.require(:booth).permit(:name)
end
end
And this is the booth model:
class Booth < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
end
I also added this to the user model:
has_one :booth, dependent: :destroy
When I include validates :user_id, presence: true it won't save to the db. When I exclude it, it saves but does not include a user id in the database. If you are still reading thank you and I hope you can help!
You need to change create method of your BoothsController to this:
def create
#booth = current_user.build_booth(booth_params)
if #booth.save
flash[:success] = "Congrats on opening your booth!"
redirect_to root_url
else
render 'new'
end
end
Here, you have one-to-one association between user and booth, and that's why you have to instantiate booth for current_user using build_<singular_association_name>, which is build_booth and pass params to it: build_booth(booth_params).
booths.build(booth_params) works for one-to-many association, for example: user has many booths, not vice a versa.
I have a User who has many Accounts through a User_Accounts model. The User_Accounts model also tracks other information such as admin and billing access. Via the user edit form, I want to be able to edit the admin and billing boolean fields for the users current account.
user.rb
class User < ActiveRecord::Base
has_one :owned_account, class_name: 'Account', foreign_key: 'owner_id'
has_many :user_accounts
has_many :accounts, through: :user_accounts
accepts_nested_attributes_for :user_accounts
end
user_account.rb
class UserAccount < ActiveRecord::Base
belongs_to :account
belongs_to :user
end
In the users controller, I specified which user_account I wanted to edit via the nested form and assigned to the #user_account instance variable.
users_controller.rb
def edit
#user = User.find(params[:id])
#user_account = #user.user_accounts.find_by_account_id(current_account)
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to #user, notice: 'User was successfully updated.'
else
render action: "edit"
end
end
private
def user_params
params.require(:user).permit(:name, :email, user_accounts_attributes: [:admin, :billing] )
end
user/edit.html.erb
<%= f.fields_for :user_accounts, #user_account do |o| %>
<%= o.check_box :admin, class: 'checkbox' %>
<% end %>
When I submit the change, it successfully saves the user record, but doesn't update the User_account record. It appears to be passing the following:
{"name"=>"Colin 21", "email"=>"mike21#example.com", "user_accounts_attributes"=>{"0"=>{"admin"=>"1"}}}
Id is required to edit an object via accepts_nested_attributes_for. Seems that the id attribute is not allowed through strong parameters.
Try changing the user_params method in 'users_controller.rb'
def user_params
params.require(:user).permit(:name, :email, user_accounts_attributes: [:id, :admin, :billing] )
end
You need to add a hidden field for user account's id field within fields_for part of the form too.
I have a form that lets me create new blog posts and I'd like to be able to create new categories from the same form.
I have a habtm relationship between posts and categories, which is why I'm having trouble with this.
I have the following 2 models:
class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
attr_accessible :title, :body, :category_ids
accepts_nested_attributes_for :categories # should this be singular?
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
attr_accessible :name
end
My form lets me pick from a bunch of existing categories or create a brand new one. My form is as follows.
# using simple_form gem
.inputs
= f.input :title
= f.input :body
# the line below lets me choose from existing categories
= f.association :categories, :label => 'Filed Under'
# I was hoping that the code below would let me create new categories
= f.fields_for :category do |builder|
= builder.label :content, "Name"
= builder.text_field :content
When I submit my form, it gets processed but the new category is not created. My command prompt output tells me:
WARNING: Can't mass-assign protected attributes: category
But, if I add attr_accessible :category, I get a big fat crash with error message "unknown attribute: category".
If I change the fields_for target to :categories (instead of category) then my form doesn't even display.
I've spent a while trying to figure this out, and watched the recent railscasts on nested_models and simple_form but couldn't get my problem fixed.
Would this be easier if I was using a has_many :through relationship (with a join model) instead of a habtm?
Thanks to everyone who answered. After much trial and error, I managed to come up with a fix.
First of all, I switched from a HABTM to a has_many :through relationship, calling my join model categorization.rb (instead of categorizations_posts.rb) - NB: the fix detailed below will likely work with a HABTM too:
Step 1: I changed my models to look like this:
# post.rb
class Post < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations
attr_accessible :title, :body, :category_ids
accepts_nested_attributes_for :categories
end
#category.rb
class Category < ActiveRecord::Base
has_many :categorizations
has_many :posts, :through => :categorizations
attr_accessible :name, :post_ids
end
#categorization.rb
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
end
From the post model above: obviously, the accessor named :category_ids must be present if you want to enable selecting multiple existing categories, but you do not need an accessor method for creating new categories... I didn't know that.
Step 2: I changed my view to look like this:
-# just showing the relevent parts
= fields_for :category do |builder|
= builder.label :name, "Name"
= builder.text_field :name
From the view code above, it's important to note the use of fields_for :category as opposed to the somewhat unintuitive fields_for :categories_attributes
Step 3
Finally, I added some code to my controller:
# POST /posts
# POST /posts.xml
def create
#post = Post.new(params[:post])
#category = #post.categories.build(params[:category]) unless params[:category][:name].blank?
# stuff removed
end
def update
#post = Post.find(params[:id])
#category = #post.categories.build(params[:category]) unless params[:category][:name].blank?
# stuff removed
end
Now, when I create a new post, I can simultaneously choose multiple existing categories from the select menu and create a brand new category at the same time - it's not a case of one-or-the-other
There is one tiny bug which only occurs when editing and updating existing posts; in this case it won't let me simultaneously create a new category and select multiple existing categories - if I try to do both at the same time, then only the existing categories are associated with the post, and the brand-new one is rejected (with no error message). But I can get round this by editing the post twice, once to create the new category (which automagically associates it with the post) and then a second time to select some additional existing categories from the menu - like I said this is not a big deal because it all works really well otherwise and my users can adapt to these limits
Anyway, I hope this helps someone.
Amen.
In your form you probably should render the fields_for once per category (you can have multiple categories per post, hence the habtm relation). Try something like:
- for category in #post.categories
= fields_for "post[categories_attributes][#{category.new_record? ? category.object_id : category.id}]", category do |builder|
= builder.hidden_field :id unless category.new_record?
= builder.label :content, "Name"
= builder.text_field :content
I have made my application and my nested form works with HABTM.
My model is :
class UserProfile < ActiveRecord::Base
attr_accessible :name, :profession
has_and_belongs_to_many :cities
belongs_to :user
attr_accessible :city_ids, :cities
def self.check_city(user,city)
user.cities.find_by_id(city.id).present?
end
end
class City < ActiveRecord::Base
attr_accessible :city_name
has_and_belongs_to_many :user_profiles
end
In my form I have:
-# just showing the relevent parts
= f.fields_for :cities do|city|
= city.text_field :city_name
And at my controller:
def create
params[:user_profile][:city_ids] ||= []
if params[:user_profile][:cities][:city_name].present?
#city= City.create(:city_name=>params[:user_profile][:cities][:city_name])
#city.save
params[:user_profile][:city_ids] << #city.id
end
#user=current_user
params[:user_profile].delete(:cities)
#user_profile = #user.build_user_profile(params[:user_profile])
respond_to do |format|
if #user_profile.save
format.html { redirect_to #user_profile, notice: 'User profile was successfully created.' }
format.json { render json: #user_profile, status: :created, location: #user_profile }
else
format.html { render action: "new" }
format.json { render json: #user_profile.errors, status: :unprocessable_entity }
end
end
end
def update
params[:user_profile][:city_ids] ||= []
if params[:user_profile][:cities][:city_name].present?
#city= City.create(:city_name=>params[:user_profile][:cities][:city_name])
#city.save
params[:user_profile][:city_ids] << #city.id
end
#user=current_user
params[:user_profile].delete(:cities)
#user_profile = #user.user_profile
respond_to do |format|
if #user_profile.update_attributes(params[:user_profile])
format.html { redirect_to #user_profile, notice: 'User profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #user_profile.errors, status: :unprocessable_entity }
end
end
end
This code works.
Maybe you should try it with (not testet):
attr_accessible :category_attributes
And HBTM relations arent really recommened... But I use them on my own :P