Create multiple objects simultaneously using for loop in controller - ruby-on-rails

I have a Word model with one column: word. I have a form that creates an #word object when submitted.
words/_form.html.erb
<%= form_for(#word, :remote => (params[:action] == 'new' ? true : false)) do |f| %>
<fieldset>
<div class="field">
<%= f.text_field :word, :required => true %>
</div>
</fieldset>
<div class="actions">
<%= f.submit :disable_with => 'Submitting...' %>
</div>
<% end %>
words/create.js.erb
$('#words').prepend( '<%= escape_javascript(render #word) %>' );
$('#new-word-form-container').find('input:not(:submit),select,textarea').val('');
I'd like to shorthand the creation of multiple words simontaniously on one form submission (i.e. instead of having to resubmit to create each individual word).
I have a method in my Word model that splits the string into an array of words (separated by a comma or space).
class Word < ActiveRecord::Base
attr_accessible :word
# Split Words method splits words seperated by a comma or space
def self.split_words(word)
# Determine if multiple words
if word.match(/[\s,]+/)
word = word.split(/[\s,]+/) # Split and return array of words
else
word = word.split # String => array
end
end
end
I'm trying to use a for loop within my create action to step through each array element, and create an #word object for the element.
class WordsController < ApplicationController
respond_to :js, :json, :html
def create
split = Word.split_words(params[:word])
split.each do |w|
#word = Word.create(params[:w])
respond_with(#word)
end
end
I'm currently getting a HashWithIndifferentAccess error, as listed below.
Started POST "/words" for 127.0.0.1 at 2014-06-10 13:09:26 -0400
Processing by WordsController#create as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"0hOmyrQfFWHRkBt8hYs7zKuHjCwYhYdv444Zl+GWzEA=", "word"=>{"word"=>"stack, overflow"}, "commit"=>"Create Word"}
Completed 500 Internal Server Error in 0ms
NoMethodError (undefined method `match' for {"word"=>"stack, overflow"}:ActiveSupport::HashWithIndifferentAccess):
app/models/word.rb:9:in `split_words'
app/controllers/words_controller.rb:36:in `create'
Any help is greatly appreciated.

In your create action in the words controller, you fetch the words from the params which gives you back a parameter object. The parameter object is a hash like object that inherits from ActiveSupport::HashWithIndifferentAccess. Then you try and call the match method on your parameter object and it does not know how to respond to it, so you get a NoMethodError.
Checkout http://api.rubyonrails.org/classes/ActionController/Parameters.html
The first thing you need to do is pass params[:word][:word] instead of params[:word], this should give you back a string object and this method should now work.
It also looks like you might run into another problem in the each loop in create as params[:w] might return nil. You should instead just pass in w because that will be each word in the array you are iterating over and if am not mistaken you want to create a word object for each word.
def create
split = Word.split_words(params[:word][:word])
#words = split.map do |w|
Word.create(word: w)
end
respond_with(#words)
end

class WordsController < ApplicationController
respond_to :js, :json, :html
def create
#words = params[:word].split.map { |word| Word.create(word) }
respond_with(#words)
end
end

Related

Ruby MVC and text_field

I have this code which I don't really understand:
app\controllers\look_controller.rb
class LookController < ApplicationController
def at
#data_hash = params[:cruncher]
#cruncher = Cruncher.new(#data_hash[:crunch])
#data = #cruncher.crunch
end
def input
end
end
app\models\cruncher.rb
class Cruncher
attr_reader :crunch
attr_writer :crunch
def initialize(data)
#crunch = data
end
end
app\views\look\input.rhtml:
<html>
<head>
<title>Using Text Fields</title>
</head>
<body>
<h1>Working With Text Fields</h1>
This Ruby on Rails application lets you read data from text fields.
<br>
<%= start_form_tag ({:action => “at”}, {:method => “post”}) %>
Please enter your name.
<br>
<%= text_field (“cruncher”, “crunch”, {“size” => 30}) %>
<br>
<br>
<input type=”submit”/>
<%= end_form_tag %>
</body>
</html>
I do not understand what is the relationship between <%= text_field (“cruncher”, “crunch”, {“size” => 30}) %> and the model. What do text_fields attributes cruncher and crunch have to do with the model?
As I understand the params is a special hash that stores the data from the user, and by using #data_hash = params[:hash] inside the controller we store that data.
But what about this #cruncher = Cruncher.new(#data_hash[:crunch]), why do we now use #data_hash[:crunch]?
Why not just #data_hash?
Thanks.
if you look at he html produced by the input view, you'll see something like this for the text field:
<input type="text" name="cruncher_crunch" value="cruncher[crunch]" size="30" />
this means that the params hash, created when the form is submitted, and sent to the LookController#at method will be formatted like this:
{cruncher: {crunch: 'somevalue'}}
which is exactly the format that the Cruncher.new(#data_hash[:cruncher]) expects.
Its not that strange that you don't understand it.
This code is probably ludicrously old (.rhtml and start_form_tag put it at Rails 1 or 2) and really bad, it does not even run as there are two syntax errors as well as the quotes that look like an artifact from pasting the code into MS Word
# don't put a space before parens when calling methods in Ruby!
text_field (“cruncher”, “crunch”, {“size” => 30})
It would also give NoMethodError on #data = #cruncher.crunch.
In Rails 5 the same example can be written as:
class Cruncher
include ActiveModel::Model
attr_accessor :crunch
def crunch
# have no idea what this was supposed to do
end
end
class LookController < ApplicationController
def at
#cruncher = Cruncher.new(cruncher_params)
#data = #cruncher.crunch
end
private
def cruncher_params
params.fetch(:cruncher).permit(:crunch)
end
end
# I really have no idea what the actual routes are supposed to be
<%= form_for(#cruncher, url: '/look/at') do %>
<%= f.text_field(:crunch size: 30) %>
<% end %>
Its still just a strange and non RESTful example though. Sometimes garbage code is best left buried.
I do not understand what is the relationship between <%= text_field
(“cruncher”, “crunch”, {“size” => 30}) %> and the model. What do
text_fields attributes cruncher and crunch have to do with the model?
Nothing. There is no data binding. Its just a plain-jane text input.
But what about this #cruncher = Cruncher.new(#data_hash[:crunch]), why
do we now use #data_hash[:crunch]?
Because the author didn't know what they where doing. And probably had not figured out that you can pass hashes to your methods.

Rails: simple 'Setting' model with key, value attributes

I’m new to rails and I’ve a pretty simple situation to solve but I cannot figure out how to proceed with it.
I want to create a simple ‘Setting’ model with key, value attributes. ‘SettingsController’ may contain 2 public methods only index and update. Only index action will have a view file with a form whose fields will represent each record of the ‘Settings’ table.
I want to be able to define some permitted keys (may be using some private method) and I want the form to create or update the record of relevant fields on submitting the form to update action.
Now, I don’t know exactly what code should I use in controller for index and update actions and in the index view file for the form which can create/update multiple records at the same time and can show updated values all the time. How do I proceed with it?
Update # 1:
I've managed to write some controller actions as below (based on some tutorial):
class Admin::SettingsController < ApplicationController
def index
#settings = Setting.all
end
def update
setting_params.each do |key, value|
Setting.where(key: key).first.update_attribute :value, value
end
redirect_to admin_settings_path, notice: "Settings saved."
end
private
def setting_params
params.require(:settings).permit(:site_title, :site_desc)
end
end
The form code in index view template is given below:
<h1>Settings</h1>
<%= form_tag admin_settings_path, method: "put" do %>
<p>
<label>Site Title:</label>
<%= text_field_tag "settings[site_title]" %>
</p>
<p>
<label>Site Description:</label>
<%= text_field_tag "settings[site_desc]" %>
</p>
<p>
<%= submit_tag "Save settings" %>
</p>
<% end %>
This forms saves the values correctly in the database but the saved values doesn't persist in form fields.
Maybe the problem is in the index action. Setting.all returns an array of Setting record, not a hash like { key1: value, key2: value } which I think you are trying to achieve. The form, therefore, displays data improperly. You can try this:
def index
#setting = {}
pairs = Setting.pluck(:key, :value)
pairs.each { |key, value| #setting[key] = value }
#setting
end

"First argument in form cannot contain nil or be empty"

I have difficulties with ruby on rails syntax.
I got this error
First argument in form cannot contain nil or be empty
class PersonalsController
def index
end
def create
#personal = Personal.new
end
def new
#personal = Personal.new
end
def show
#personal = Personal.find([:id])
end
end
index.html.erb
<%= form_for #personal do |f| %>
<%= f.label :title %><br>
<%= f.text_field :title %>
<%= f.submit %>
<% end %>
the value of #personal is nil that's why you are getting error.
Change you code like this
def index
#personal= Personal.all
end
form_for is helper method
check with this link form_helper
The error is generated since #personal was not set in the controller. So either you add a #personal = Personal.new to the index method, or set it to a specific database entry, e.g., #personal = Personal.find(1)
However, it seems strange that you have a form displaying a single record in the index view.
More likely, you want to have the form in your new or edit views (in the former case you typically use new, while in the latter case you would use the find method to find a specific record and let the user edit it).
In the index method, you usually use the controller to select a group of records (e.g., #ps = Personal.all to get all the records) and iterate over them in the view (#ps.each do |person| .... end)
P.S. The show method should probably use Personal.find(params[:id]) instead of Personal.find([:id])

Getting NoMethodError, how do I define this method?

I am using a virtual attribute called :all_dates on my form . The point of this field is to replace the :purchase_date attribute of my UserPrice model with the date of my :all_dates field. The reason for this is so user's don't have to change the :purchase_date of all of the user_price records they want to create on the form (they can create a maximum of 5), so what it suppose to do is update the columns of the user_prices with the date that is given from the :all_dates field.
Problem
Unfortunately on creating 1 to 5 records of user_prices, I get a NoMethodError because of the :all_dates field:
NoMethodError (undefined method `user_prices' for #<UserPrice:0x485d918>):
app/models/user_price.rb:54:in `save_all_dates_to_user_prices'
app/controllers/user_prices_controller.rb:27:in `each'
app/controllers/user_prices_controller.rb:27:in `create_multiple'
UPDATE
I got rid of the NoMethodError by putting this in my UserPrice model:
def user_prices
#user_prices = Array.new() { UserPrice.new }
end
But that isn't correct because the :all_dates field doesn't update my UserPrice :purchase_date fields. Does anyone have any ideas?
Question
How do I define the method user_prices?
I am guessing its suppose to be able to loop several new records of UserPrice but how is that done?
Code
This form acts like a nested form but instead of using two or more models its just using one single model which is my UserPrice to generate more records on the form, in my case being 5 new ones.
<%= form_tag create_multiple_user_prices_path, :method => :post do %>
<%= date_select("user_price", "all_dates" %>
<% #user_prices.each_with_index do |user_price, index| %>
<%= fields_for "user_prices[#{index}]", user_price do |up| %>
<%= render "add_store_price_fields", :f => up %>
<% end %>
<% end %>
<% end %>
class UserPrice < ActiveRecord::Base
attr_accessible :price, :product_name, :all_dates
attr_accessor :all_dates
after_save :save_all_dates_to_user_prices
protected
def save_all_dates_to_user_prices
self.user_prices.each {|up| up.purchase_date = self.all_dates if up.new_record?}
end
class UserPricesController < ApplicationController
def new
#user_prices = Array.new(5) { UserPrice.new }
end
def create_multiple
#user_prices = params[:user_prices].values.collect { |up| UserPrice.new(up) }
if #user_prices.all?(&:valid?)
#user_prices.each(&:save!)
redirect_to :back, :notice => "Successfully added prices."
else
redirect_to :back, :notice => "Error, please try again."
end
end
Re: Why receiving error undefined method `user_prices' for...
Ans: You need to define the method user_prices
Since you named the model (object) UserPrice, normally user_price would be used to represent an instance of the model.
You need to re-think what user_prices represents, an array of UserPrice objects/records? Or something else?
Added Do you want method save_all_dates_to_user_prices to iterate through all of the UserPrice records?
If so, then:
You probably want save_all_dates_to_user_prices to be a class method since it would be dealing with the multiple instances of the class.
The method would need to first load an array with all of the current records. Do this with the class method find or scope
I Took a totally different approach and was still able to get the same results in this Question: How to update a model's attribute with a virtual attribute?

Edit a serialized hash in a form?

I'm serializing a hash that is stored in a settings field in a table, and would like to be able to edit that hash in a form field.
class Template < ActiveRecord::Base
serialize :settings
end
But I just do <%= f.text_area :settings %> then the text area just shows the serialized data instead of the hash.
How can I get the hash to show in the text area?
Maybe setting up another accessor for your model would work.
class Template < ActiveRecord::Base
serialize :settings
attr_accessor :settings_edit
before_save :handle_settings_edit, :if => lambda {|template| template.settings_edit.present? }
def settings_edit
read_attribute(:settings).inspect # should display your hash like you want
end
protected
def handle_settings_edit
# You may want to perform eval in your validations instead of in a
# before_save callback, so that you can show errors on your form.
begin
self.settings = eval(settings_edit)
rescue SyntaxError => e
self.settings = settings_edit
end
end
end
Then in your form use <%= f.text_area :settings_edit %>.
I have not tested any of this code, but in theory it should work. Good luck!
WARNING: Using eval like this is very dangerous, in this example a user could delete the entire Template table with one line in the edit box Template.destroy_all. Use a different method to convert the string to a hash if user input is involved.
... or you could use something like this (without any logic in model):
<% #template.settings.each do |name, value| %>
<div>
<%= label_tag name %>
<%= text_field_tag "template[settings][#{name}]", value %>
</div>
<% end %>
you should use something like
class Template < ActiveRecord::Base
serialize :settings, Hash
end

Resources