Creating multiple records with one form - ruby-on-rails

I'm trying to get a nested form to work but keep getting the error below:
ActiveModel::MassAssignmentSecurity::Error in ResponsesController#create
Can't mass-assign protected attributes: gate_answer
Here are my models... what am I doing wrong?
class Response < ActiveRecord::Base
attr_accessible :gate_id, :gate_answers_attributes
belongs_to :gate
has_many :gate_answers
accepts_nested_attributes_for :gate_answers
end
class GateAnswer < ActiveRecord::Base
attr_accessible :prospect_id, :gate_question_id, :content, :response_id
belongs_to :response
end
And the DB schema extract:
create_table "gate_answers", :force => true do |t|
t.integer "prospect_id"
t.integer "gate_question_id"
t.text "content"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "response_id"
end
create_table "responses", :force => true do |t|
t.integer "gate_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
..and my controllers
# responses_controller.rb
def create
#response = Response.new(params[:response])
respond_to do |format|
if #response.save
format.html { redirect_to :action => 'index', :gate_id => (params[:response][:gate_id]), notice: 'Response was successfully created.' }
else
format.html { render action: "new" }
end
end
end
#gate_answers_controller.rb
def create
#gate_answer = GateAnswer.new(params[:gate_answer])
#code below finds URL that user will be redirected to after form is saved
#gate = Gate.find(params[:gate_answer][:gate_id])
respond_to do |format|
if #gate_answer.save
format.html { redirect_to #gate.content_url, notice: 'Redirecting.' } #redirect to URL per above
else
format.html { render action: "new" }
end
end
end
What am I doing wrong?

Try Form Object approach from here http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

A way of creating this type of form is shown in detail in the Railscasts Nested Model Form Part 1 & Part 2. Maybe this will help you?

Related

Query in Active Record Rails does not work

I am trying to use Rails' active record to generate something before_save and save it to a field. It's using two tables (Message and Spec)
My models/message.rb file looks like this:
class Message < ApplicationRecord
has_many :specs
accepts_nested_attributes_for :specs
before_save :generate_output
def generate_output
self.output = "hola"
specs_array = Spec.where(message_id: self.id).order('id asc')
specs_array.each do |spec|
self.output = "hello"
if spec.call
self.output += Message.find(name: spec).output
else
self.output += spec.specification
end
end
self.output
end
end
and my models/spec.rb file:
class Spec < ApplicationRecord
belongs_to :message
end
And here is my schema:
ActiveRecord::Schema.define(version: 20171121153642) do
enable_extension "plpgsql"
create_table "messages", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "output"
end
create_table "specs", force: :cascade do |t|
t.string "specification"
t.boolean "call"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "message_id"
t.index ["message_id"], name: "index_specs_on_message_id"
end
add_foreign_key "specs", "messages"
end
I have a message form, that upon submission, saves a 'name' to Message table and 3 Specifications (and their message_id) to Spec table. It should also generate and save an output (as you can see in the message model) based on the specs in the Message table.
but these two lines of code in the model do not work:
specs_array = Spec.where(message_id: self.id).order('id asc')
specs_array.each do |spec|
I know the ones before them are working, because when I create a new message, its output is saved as 'hola' and if these two lines work, it should be saved as 'hello'+ whatever the message is.
I have tried the query in rails console, and it totally works fine, any idea why it's not working in the app?
thanks!
edit:
My controller method that calls for save (it's Rails generic method):
def create
#message = Message.new(message_params)
respond_to do |format|
if #message.save
format.html { redirect_to #message, notice: 'Message was successfully created.' }
format.json { render :show, status: :created, location: #message }
else
format.html { render :new }
format.json { render json: #message.errors, status: :unprocessable_entity }
end
end
end
and the private method for messasge_params:
def message_params
params.require(:message).permit(:name, specs_attributes: [:id, :call, :specification])
end
Try to rewrite your like:
specs_array = Spec.where(message_id: self.id).order('id asc')
To:
specs_array = specs.order(:id)
Write a comment whether that helped?

How to update the users balance when a user makes a transaction?

I am stock trying to write the code that make possible for users to update a balance every time they make a transaction.
This is a simple bartering application:
The user can either offer products for sale or buy products from other users.
The user pays with a kind of virtual money (units).
When a user clicks in Order a transaction is executed.
The Models are: User, Product and Order.
If the user orders a product ( here order=transaction) I expect that the orders price (here price=amount) will be added to the users balance:
My expectation is that this code in orders_controller could make that the amount of #price pass and adds to #balance and makes possible the update:
#user.balance = balance: (#user.balance += #order.price)
But this is not working
I have tried as well in orders_controller with this:
def balance
if #order.save
#user.balance_update!(balance: #user.balance + #order.price)
end
end
But doesnt work.
What could be wrong with this code?
Please help!
These are the relevant files:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :fullname,presence: true
validates :description, presence: false
validates :balance, presence: true, numericality:true
before_validation :load_defaults
def load_defaults
if self.new_record?
self.balance = 100
end
end
has_many :products
has_many :orders
end
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :product
validates :price, presence: true
validates :product_id, presence: true
validates :user, presence: true
end
class Product < ActiveRecord::Base
belongs_to :user
has_many :orders
end
class OrdersController < ApplicationController
before_action :authenticate_user!
def create
#order = current_user.orders.create(order_params)
#user.balance = balance: (#user.balance += #order.price)
redirect_to user_orders_path
end
end
def user_orders
#orders = current_user.orders
end
private
def order_params
params.require(:order).permit(:price, :user_id)
end
end
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#products = #user.products
end
end
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:show]
def index
#products = current_user.products
end
def show
end
def new
#product = current_user.products.build
end
def edit
end
def create
#product = current_user.products.build(product_params)
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render :show, status: :created, location: #product }
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to #product, notice: 'Product was successfully updated.' }
format.json { render :show, status: :ok, location: #product }
else
format.html { render :edit }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
def destroy
#product.destroy
respond_to do |format|
format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_product
#product = Product.find(params[:id])
end
def product_params
params.require(:product).permit(:name, :description, :price)
end
end
<p>
User name: <%= #user.fullname %>
</p>
<p>
Balance: <%= #user.balance %>
</p>
ActiveRecord::Schema.define(version: 20171031150052) do
create_table "orders", force: :cascade do |t|
t.integer "user_id"
t.integer "product_id"
t.integer "price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "transactiontype"
t.integer "buyer_id"
t.integer "seller_id"
end
add_index "orders", ["product_id"], name: "index_orders_on_product_id"
add_index "orders", ["user_id"], name: "index_orders_on_user_id"
create_table "products", force: :cascade do |t|
t.string "name"
t.text "description"
t.integer "price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
add_index "products", ["user_id"], name: "index_products_on_user_id"
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "fullname"
t.string "description"
t.integer "balance"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
Rails.application.routes.draw do
devise_for :users
get 'pages/index'
resources :products
resources :users
resources :products do
resources :orders, only: [:create]
end
resources :orders, only: [:show]
get '/user_orders' => 'orders#user_orders'
end
If you assign an attribute directly you just assign a value and need call save yourself:
#user.balance = #order.price
#user.save!
Or you can use update (there is no balance_update), then you call it with a hash:
#user.update!(balance: #user.balance + #order.price)
Try
#user.update(balance: #user.balance + #order.price)

New strong parameter not being saved in database

I added a new column named "version" to a table in Rails using a migration and manually added the corresponding parameter to the strong parameters permitted in the corresponding controller:
def endpoint_params
params.require(:endpoint).permit(:hostname, :username, :password, :connection_string, :entity_id, :created_at,
:updated_at, :endpoint_type_id, :endpoint_app_id, :environment_id, :created_by,
:updated_by, :version)
end
However, when I try to set that new parameter in the update action and save it to the database, it doesn't get saved at all. The code that I'm using is:
def update
begin
#environment = Environment.find(params[:env])
version_no = (EndpointsFile.where("environment_id = ?", #environment.id).maximum('version') || 0) + 1
#endpoint.updated_by = current_user.id
#endpoint.version = version_no
respond_to do |format|
if #endpoint.update(endpoint_params)
#endpoints = Endpoint.where("environment_id = ? and id <> ?", #environment.id, #endpoint.id)
EndpointsFile.create!(version: version_no, date: Time.now, environment_id: #environment.id)
#endpoints.each do |e|
e.version = version_no
e.save
puts "e.version: #{e.version}" **=> HERE IT PRINTS e.version: and the field is null in the database**
end
format.html { redirect_to env_endpoints_path(env: #environment.id), notice: t('.notice') }
format.json { render :show, status: :ok, location: #endpoint }
else
format.html { render :edit }
format.json { render json: #endpoint.errors, status: :unprocessable_entity }
end
end
rescue => exception
logger.error { "endpoints_controller.update -> Ocorreu um erro ao atualizar o endpoint: #{exception.message}" }
respond_to do |format|
format.html { redirect_to env_endpoints_path(env: #environment.id), alert: "Ocorreu um erro ao atualizar o endpoint: #{exception.message}" and return }
format.json { render json: #endpoint.errors, status: :unprocessable_entity }
end
end
end
This is the Endpoint model:
class Endpoint < ApplicationRecord
belongs_to :entity
belongs_to :environment
belongs_to :endpoint_app
belongs_to :endpoint_type
has_many :configs_histories
has_paper_trail
end
The table in DB is:
create_table "endpoints", force: :cascade do |t|
t.string "hostname"
t.string "username"
t.string "password"
t.string "connection_string"
t.integer "entity_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "endpoint_type_id"
t.integer "endpoint_app_id"
t.integer "environment_id"
t.integer "created_by"
t.integer "updated_by"
t.integer "version"
t.index ["endpoint_app_id"], name: "index_endpoints_on_endpoint_app_id"
t.index ["endpoint_type_id"], name: "index_endpoints_on_endpoint_type_id"
t.index ["entity_id"], name: "index_endpoints_on_entity_id"
t.index ["environment_id"], name: "index_endpoints_on_environment_id"
end
What am I doing wrong? Why doesn't the command e.save work?
Finally found the solution.
version is a reserved keyword so it can't be used as a column name.
check link: http://reservedwords.herokuapp.com/words?page=9
Since you are calculating version_no inside update, you don't have to pass it in controller or permit in strong parameters, just do the following
#endpoint.update(endpoint_params.merge(version: version_no))

Connecting two models in many-to-many relationship

I have a problem.
I want to get all matches in my player#show where player was engaged. So I did a many-to-many relationship.
Match model:
class Match < ActiveRecord::Base
has_many :match_schedules
has_many :players, through: :match_schedules
end
Player model:
class Player < ActiveRecord::Base
has_many :match_schedules
has_many :matches, through: :match_schedules
has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }, :default_url => "/images/:style/missing.png"
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
end
and my match_schedule model:
class MatchSchedule < ActiveRecord::Base
belongs_to :player
belongs_to :match
end
If i do something like this in rails console:
p = Player.find 1
m = p.matches.new
m.playerA = “leo”
m.playerB = “cris”
p.save
It works, i can display loop with name:
<% #player.matches.each do |match| %>
<%= match.playerA %>
<% end %>
Problem is that i don't really know how I can connect matches to player in my new form, in browser. Already i have something like this:
Players_helper:
module PlayersHelper
def player_hash(players)
hash = Hash.new
players.each do |player|
hash["#{player.first_name}" + " " + "#{player.last_name}"] = player.first_name + player.last_name
end
hash
end
end
and _form:
<div class="form-inputs">
<%= f.select :playerA, options_for_select(player_hash(#abc)) %>
<%= f.select :playerB, options_for_select(player_hash(#abc)) %>
<%= f.input :PlayerApoints %>
<%= f.input :PlayerBpoints %>
</div>
Matches controller for new and create method looks like:
def new
#match = Match.new
#abc = Player.all
end
def create
#match = Match.new(match_params)
respond_to do |format|
if #match.save
format.html { redirect_to #match, notice: 'Match was successfully created.' }
format.json { render :show, status: :created, location: #match }
else
format.html { render :new }
format.json { render json: #match.errors, status: :unprocessable_entity }
end
end
end
And my schema.rb:
ActiveRecord::Schema.define(version: 20150706185030) do
create_table "match_schedules", force: :cascade do |t|
t.integer "match_id"
t.integer "player_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "matches", force: :cascade do |t|
t.string "playerA"
t.string "playerB"
t.integer "PlayerApoints"
t.integer "PlayerBpoints"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "players", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
end
You left out your match_params hash, so I have to do some guessing but something along the lines of:
in your Match class:
class Match < ActiveRecord::Base
has_many :match_schedules
has_many :players, through: :match_schedules
accepts_nested_attributes_for :match_schedules
end
in your controller:
def new
#match = Match.new
#match.match_schedules.build
#abc = Player.all
end
def create
#match = Match.new(match_params)
respond_to do |format|
if #match.save
format.html { redirect_to #match, notice: 'Match was successfully created.' }
format.json { render :show, status: :created, location: #match }
else
format.html { render :new }
format.json { render json: #match.errors, status: :unprocessable_entity }
end
end
end
in your match_params whitelist you'll need to add:
..., :player_ids => [])
since it is an array you need to put it after the other params.
You will also have to modify your view code. Basically you want to return a match_shedules_attributes => {player_ids => [1,2]} this gives you the ability to tell the MatchSchedule table the ID of each player associated with that match_id. You can do this with a fields_for inside the form_for block. See this http://guides.rubyonrails.org/form_helpers.html
So in the create action in the Match controller it should also save two records in the MatchSchedule table, one with each player's id and the id of that match.

Why does my form_for area#new have a button that says "Update Area", not "Create Area"

I have an area model that belongs to a report model. I have built a form partial using SimpleForm. When I go to new_report_area_path(#report), I get a New Area form that works just fine. Enter the details and hit submit and it creates an area and takes you to area#show. But the button on the new area form says "Update Area" not "Create Area". Why?
config/routes.rb:
Testivate::Application.routes.draw do
resources :reports do
resources :areas
end
end
db/schema.rb:
ActiveRecord::Schema.define(:version => 20121205045544) do
create_table "areas", :force => true do |t|
t.string "name"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "report_id"
end
create_table "reports", :force => true do |t|
t.string "name"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
end
app/models/area.rb:
class Area < ActiveRecord::Base
attr_accessible :name
has_many :heuristics
belongs_to :report
end
app/models/report.rb:
class Report < ActiveRecord::Base
attr_accessible :name
has_many :heuristics
has_many :areas
end
app/controllers/areas_controller.rb:
class AreasController < ApplicationController
filter_resource_access
def new
#report = Report.find(params[:report_id])
#area = #report.areas.create
respond_to do |format|
format.html # new.html.erb
end
end
def create
#report = Report.find(params[:report_id])
#area = #report.areas.create(params[:area])
respond_to do |format|
if #area.save
format.html { redirect_to report_area_path(#report, #area), notice: 'Area was successfully created.' }
else
format.html { render action: "new" }
end
end
end
end
app/views/areas/news.html.haml:
%h1 New Area
= render 'form'
app/views/areas/_form.html.haml:
= simple_form_for [#report, #area] do |f|
= f.error_notification
= f.input :name
= f.button :submit
Instead of creating an area you should building it as it's a new action:
def new
#report = Report.find(params[:report_id])
#area = #report.areas.build # build instead of create
respond_to do |format|
format.html # new.html.erb
end
end

Resources