How do I iterate through a hash in an objects param hash? - ruby-on-rails

Rails (and coding) rookie here (I'm sure I'm just missing basic syntax structural stuff), I've created a form where the user can add as many field pairs as they like via AJAX (barely). This form will collect column titles for a 'sheet' and the associated data type (int, str... etc). The sheet will have item entries added later by users. I'm trying to make a Sheets controller create method that not only saves the title and description of the sheet, but also adds a record to the 'columns' table with the column title, data type and associated sheet id. When I submit the sheet form, I get the following params in the server terminal:
(sorry I'm not sure how to wrap the code snippet)
{"utf8"=>"✓", "authenticity_token"=>"yMlnfO1EWptkEXp5+9AGCO5C3vHt62EUHoKjdWoUB8I=", "sheet"=>{"title"=>"test 33", "description"=>"Descriptions"}, "column"=>[{"title"=>"1", "type"=>"num"}, {"title"=>"2", "type"=>"int"}, {"title"=>"3", "type"=>"real"}, {"title"=>"fo", "type"=>"no"}], "commit"=>"Save Specsheet!"}
I'm trying to loop through the column hash to create a record on the columns table. Each hash would use the title and type values as entries on the table.
My create method:
def create
#sheet = Sheet.new(sheet_params)
#sheet[:column].each do |key, value|
#column = Column.new
#column[:column_title] = key
#column[:column_data_type] = value
#column.save
end
if #sheet.save
redirect_to #sheet
else
flash[:error] = "Error saving sheet."
render :new
end
end
My error is usually something like this:
undefined method `each' for nil:NilClass
**#sheet[:column].each do |key, value|**
#column = Column.new
#column[:column_title] = key
#column[:column_data_type] = value
So I know I'm messing up referencing the column hash and its key and values. I'm thinking I can .reduce something here? I have no idea. These kinds of basic structural questions don’t really show up with googling, so please let me know what I'm doing wrong, and thank you for reading all of this! Cheers!
WORKING CODE (sorry for weird formatting)
def create
#sheet = Sheet.new(sheet_params)
column_params.each do |value|
#sheet.columns.build(value.permit(:title, :data_type))
end
if #sheet.save
redirect_to #sheet
else
flash[:error] = "Error saving sheet."
render :new
end
end
private
def sheet_params
params.require(:sheet).permit(:title, :description, :created_at, :updated_at, :column)
end
def column_params
params.require(:column)
end

When you are calling #sheet[:column], you are referencing an instance of Sheet instead of the params that you are trying to cycle through.
If you are trying to associate Columns to Sheets in a has_many relationship, you can create Columns like:
column.each do |key, value|
#sheet.columns.new(
column_title: key
column_data_type: value
end
end
(and then save block)
in your controller. Where column are the params. This will indicate that the column belongs to the sheet instance.
If you are trying to make the Column record without the associations, you can do
column.each do |key, value|
Column.new(
column_title: key
column_data_type: value
end
end
(and then save block)
(Both assuming that your fields are named column_title and not just title.)

Related

How to append an array in parameters after Rails form submit?

I have a form with checkboxes that get passed as an array "list_person_ids" on form submit. My models are "Occurance" which has an n:m relationship with "ListPerson" through the Model "Person". "list_person_ids" are saved in Person with the Occurance id and the ListPerson id.
I want to append one or more values to the array before this gets saved. The reason I need to do this is because the user can also add a new value in ListPerson using a textbox with the name "person_name".
def create
#occurance = Occurance.new(occurance_params)
add_person_id(#occurance)
...
# save
end
def add_person_id(object)
if params[:person_check] == '1'
object.list_person_ids.push( ListPerson.find_or_create_by(person: params[:person_name]).id )
end
end
def occurance_params
params.require(:occurance).permit(:person_check, :person_name, dim_person_ids: [])
end
find_or_create_by is successful, but nothing gets pushed to the array "list_person_ids". There is also no error message. Do I have to give permission somewhere to append this array? Please let me know if some information is missing.
on your model you can do something like below:
....
before_create :create_lists
private
def create_lists
ListPerson.find_or_create_by(list_person_ids: person_name.id)
end

How to save each value from an array to database in Rails?

I would like to store each value from an array.
For example the form sends me this data:
"attendance"=>{"event_id"=>"6", "member_id"=>["16", "28", "26"]}
I'd like the database to store the data as:
INSERT INTO "attendances" ("event_id", "member_id") VALUES ("6", "16")
INSERT INTO "attendances" ("event_id", "member_id") VALUES ("6", "28")
INSERT INTO "attendances" ("event_id", "member_id") VALUES ("6", "26")
I've tried to use the usual way of inserting data in Rails, but it failed because the member_ids didn't get passed (I've tried to print the member_ids after the Attendance.new(attendance_params)):
def create
#attendance = Attendance.new(attendance_params)
# puts #attendance[:event_id]
# puts #attendance[:member_id] -> Nothing showed up here.
if #attendance.save
flash[:success] = "Successfully created"
redirect_to new_attendance_path
else
#error_msg = #attendance.errors.full_messages
flash[:error] = #error_msg # Prints ["Member must exist"]
redirect_to new_attendance_path
end
end
I've also tried creating a new function in the model to change the Attendance.new but it'll return
NoMethodError - undefined method `new_each' for #<Class:0x000000000d1e7280>: app/controllers/attendances_controller.rb:17:in `create'
This is my current model:
class Attendance < ApplicationRecord
belongs_to :event
belongs_to :member
# def new_each(attendance)
# attendance_event = attenance[:event_id]
# attendance_members = attendance[:member_id]
# I tried to iterate and save each data here.
# end
end
So, how do I save each value from an array input (from the form) and save it to database?
Any answers and comment will be very appreciated.
You are trying to add attendance in bulk, For that you can do 2 things,
use gem bulk_insert, it's very easy to use here is the link https://github.com/jamis/bulk_insert
you need to iterate through all the members and events from params and create a record for each. though it's a very dirty way.
sample code would be like this. you can modify it as per your need.
errors = {}
attendance_params[:member_id].each do |member_id|
attendence = Attendence.new(event_id:attendance_params[:event_id], member_id: member_id)
errors[member_id] = "Could not save attendance, error =
attendance.errors.full_messages" unless
attendence.save
end

How do you insert a input name="title0" in the title column in the rails column?

How do you insert a input name="title0" in the title column in the rails column?
plz using params.require().permit()
#bank = Bank.new(params.require(:bank).permit(:title))
this is POST title => DB column title
but I want is POST title0 => DB column title
Sorry, i am bad at english
First, refactor your code as described here. So, your code will become
def probably_create_action
#bank = Bank.new(bank_params)
end
...
private
def bank_params
params.require(:bank).permit(:param1, :param2, :param3, :title0)
end
Attributes not listed inside bank_params method will not be passed.
UPDATE
Fighting with conventions is not a good practice. But below snippet might save your day.
#app/models/bank.rb
def title0=(inp)
self.title = inp
end

what var type to dynamically access Model's attribute from another controller? (Rails 4.2)

Goal: dynamically update another Model's properties (Tracker) from Controller (cards_controller.rb), when cards_controller is running the def update action.
Error I receive : NameError in CardsController#update, and it calls out the 2nd last line in the
def update_tracker(card_attribute) :
updated_array = #tracker.instance_variable_get("#{string_tracker_column}")[Time.zone.now, #card.(eval(card_attribute.to_s))]
Perceived problem: I have everything working except that I don't know the appropriate way to 'call' the attribute of Tracker correctly, when using dynamic attributes.
The attribute of the Tracker is an array (using PG as db works fine), I want to
figure out what property has been changed (works)
read the corresponding property array from Tracker's model, and make a local var from it. (works I think, )
push() a new array to the local var. This new array contains the datetime (of now) and, a string (with the value of the updated string of the Card) (works)
updated the Tracker with the correct attribute.
With the following code from the cards_controller.rb
it's the if #card.deck.tracked in the update method that makes the process start
cards_controller.rb
...
def update
#card = Card.find(params[:id])
if #card.deck.tracked
detect_changes
end
if #card.update_attributes(card_params)
if #card.deck.tracked
prop_changed?
end
flash[:success] = "Card info updated."
respond_to do |format|
format.html { render 'show' }
end
else
render 'edit'
end
end
...
private
def detect_changes
#changed = []
#changed << :front if #card.front != params[:card][:front]
#changed << :hint if #card.hint != params[:card][:hint]
#changed << :back if #card.back != params[:card][:back]
end
def prop_changed?
#changed.each do |check|
#changed.include? check
puts "Following property has been changed : #{check}"
update_tracker(check)
end
end
def update_tracker(card_attribute)
tracker_attribute = case card_attribute
when :front; :front_changed
when :back; :back_changed
when :hint; :hint_changed
end
string_tracker_column = tracker_attribute.to_s
#tracker ||= Tracker.find_by(card_id: #card.id)
updated_array = #tracker.instance_variable_get("#{string_tracker_column}")[Time.zone.now, #card.(eval(card_attribute.to_s))]
#tracker.update_attribute(tracker_attribute, updated_array)
end
Edit: For clarity here's the app/models/tracker.rb:
class Tracker < ActiveRecord::Base
belongs_to :card
end
Your use of instance_variable_get has been corrected, however this approach is destined to fail because ActiveRecord column values aren't stored as individual instance variables.
You can use
#tracker[string_column_changed]
#card[card_attribute]
To retrieve attribute values by name. If you want to get an association, use public_send. The latter is also useful if there is some accessor wrapping the column value (eg carrierwave)
From your error it seem your issue is this:
#tracker.instance_variable_get("#{string_tracker_column}")
evaluates to this after string interpolation:
#tracker.instance_variable_get("front_changed")
which is incorrect use of instance_variable_get. It needs an # prepended:
#tracker.instance_variable_get("#front_changed")
Seems like using instance_variable_get is unnecessary, though, if you set attr_reader :front_changed on the Tracker model.

Build array of objects from params hash after Transaction block fails, Rails

I have a form where a user can update multiple resources at the same time. The transaction block makes the form atomic: if one validation fails for any of the resources being updated, then none of the resources get updated, and active record rollsback all changes.
When transaction fails, I want to render the form again, display the same input that the user entered along with errors next to each of the error input fields which prevented the transaction from going through.
The transaction block works. What I am having trouble with is building the array of objects from the params log. Each index of the array should contain a hash which holds key/value pairs of all the attributes of a specific resource.
UDPATE: BELOW IS THE ANSWER CODE THANKS TO THE RESPONSES
Code:
def update_multiple
begin
User.transaction do
params[:users].each do |k, v|
User.find(k).update!(v)
end
flash[:notice] = "Update Successful"
redirect_to :users and return
end
rescue
#users = []
params[:users].each do |k,v|
#users.push(User.new({:id => k}.merge(v)))
end
flash[:error] = "Errors found"
render :edit_multiple and return
end
end
And for good measure, here is what the passed in parameters looks like in the log. This transaction fails because the name attribute must be at least 3 characters long.
Parameters: {"utf8"=>"✓", "authenticity_token"=>"xyz=", "users"=>{"15"=>
{"name"=>"Neil", "age"=>"11"}, "16"=>{"name"=>"z", "age"=>"33"}, "17"=>
{"name"=>"John", "age"=>"99"}}, "commit"=>"Submit Changes"}
Thanks in advance! Any way to write this code better in ruby is much appreciated as well.
Ok, so you're trying to iterate through a list of objects in your params using a for_each and an external iterator, you really don't want to do that. I'd suggest something like this:
params[:users].each do |k,v|
# k is the "key" of each user while v is the values associated with each key
#users.push(User.new(:id => k, v)
# I'm doing this in my head so you might need:
# #users.push(User.new({:id => k}.merge(v))
# if you start getting errors about looking for a proper hash or something
# I can't remember how good Rails/Ruby is at recognizing nested hashes
end
That should produce a new user for each user object passed in using the ID provided and the values associated with each value.

Resources