Getting data and post it from and to External API Rails - ruby-on-rails

I was success to get data from external API in rails,
# products_controller.rb
def load_detail_product
#response = HTTParty.get("http://localhost:3000/api/v1/products/#{params[:id]}",:headers =>{'Content-Type' => 'application/json'})
#detail_products = #response.parsed_response
#product = #detail_products['data']['product']
#product.each do |p|
#product_id = p['id']
end
end
In view, when I want create update form I just do like this
<%= link_to 'Edit', edit_product_path(#product_id) %>
but when I call it to _form.html.erb Im geing this error
undefined method `model_name' for #<Hash:0x00007ffb71715cb8>
# _form.html.erb
<%= form_for #product, authenticity_token: false do |form| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
how I get data from external API and put it on form_for and update the data?
my response API
# localhost:3000/api/v1/products/1
{
"messages": "Product Loaded Successfully",
"is_success": true,
"data": {
"product": [
{
"id": 1,
"name": "Chair",
"price": "200000",
"created_at": "2022-03-22T09:24:40.796Z",
"updated_at": "2022-03-22T09:24:40.796Z"
}
]
}
}
# for update PUT localhost:3000/api/v1/products/1
{
"product":
{
"name":"Chair",
"price":20000
}
}

The main problem here is that you're not creating an abstraction layer between your application and the outside collaborator. By performing a HTTP query directly from your controller and passing the results straight to the view you're making a strong coupling between your application and the API and any changes to the collaborator can break large portions of your application. This creates a fat controller and pushes all the complexity of dealing with the API responses down into the view which both are very bad things.
I would start by actually creating a model that represents a the data in your application:
# app/models/product.rb
class Product
include ActiveModel::Model
include ActiveModel::Attributes
attribute :id
attribute :name
# ...
def persisted?
true
end
end
Note that it doesn't inherit from ApplicationRecord - this is a model thats not backed by a database table and instead just uses the API's that rails provide to make it quack like a model - this means it will work right out the box with forms:
<%= form_with(model: #product) do |f| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The persisted? method tells the helpers that we updating a model and that it should route to product_path(id) and use PATCH.
But you also need to move the HTTP call out of the controller into a separate class:
# app/clients/products_client.rb
class ProductsClient
include HTTParty
base_url "http://localhost:3000/api/v1/products/"
format :json
attr_reader :response
# Get a product from the remote API
# GET /api/v1/products/:id
def show(id)
#response = self.class.get(id)
if #response.success?
#product = Product.new(product_params) # normalize the API data
else
nil # #todo handle 404 errors and other problems
end
end
# Send a PATCH request to update the product on the remote API
# PATCH /api/v1/products/:id
def update(product)
#response = self.class.patch(
product.id,
body: product.attributes
)
# #todo handle errors better
#response.success?
end
private
def product_params
#response['data']['product'].slice("id")
end
end
This isn't necissarily the only way or even the right way to write this class. The main point is just that you should not be burdoning your controller with more responsibilies. It has tons of jobs already.
This http client is the only component that should be touching the application boundy and have knowledge of the API. It can be tested in isolation and stubbed out when needed.
Your controller then "talks" to the API only though this client:
class ProductsController < ApplicationController
before_action :set_product, only: [:edit, :update] # ...
# GET /products/:id/edit
def edit
end
# PATCH /products/:id
def update
#product.assign_attributes(product_params)
if #product.valid? && ProductsClient.new.update(product)
redirect_to "/somewhere"
else
render :edit
end
end
private
def set_product
#product = ProductsClient.new.get(params[:id])
# resuse the existing error handling
raise ActiveRecord::RecordNotFound unless #product
end
def product_params
params.require(:product)
.permit(:name) # ...
end
end

Related

Faraday rails started with it

I have to show data from this website: https://baconipsum.com/json-api/ , but I don't know where to write the code of it in my app. What code do I have to write for controllers and in views?
Setup faraday:
bacon = Faraday.new("https://baconipsum.com/") do |f|
f.response :json # automatically parse responses as json
end
Send a request in the controller:
#bacon = bacon.get("api/", type: 'all-meat', sentences: 1).body # => ["Leberkas frankfurter chicken tongue."]
Use it in the view:
<% #bacon.each do |meat| %>
<p>
<%= meat %>
</p>
<% end %>
https://lostisland.github.io/faraday/usage/
Update
There are many ways to set it up. Very simple set up could look like this:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
private
# NOTE: this method will be accessible in any controller
# that inherits from ApplicationController
def baconipsum
# NOTE: memoize for a bit of performance, in case you're
# making multiple calls to this method.
#baconipsum ||= Faraday.new("https://baconipsum.com/") do |f|
f.response :json
end
end
end
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def show
#article = Article.find(params[:id])
# I'm not quite sure what you're trying to do with it;
# change this to fit your use case.
#bacon = baconipsum.get("api/", type: 'all-meat').body
end
end
# app/views/articles/show.html.erb
<% #bacon.each do |meat| %>
<p> <%= meat %> </p>
<% end %>

Rails can't create new object inside of ActiveAdmin controller

I'm working on messaging system between User and AdminUser. The User part is ready now I'm struggling how to allow Admin to send a reply to a conversation started by a User, inside of ActiveAdmin.
Code below:
# app/admin/conversations.rb
ActiveAdmin.register Conversation do
decorate_with ConversationDecorator
# ...
controller do
def show
super
#message = #conversation.messages.build
end
end
end
app/views/admin/conversations/_show.html.erb
# ...
<%= form_for [#conversation, #message] do |f| %>
<%= f.text_area :body %>
<%= f.text_field :messageable_id, value: current_user.id, type: "hidden" %>
<%= f.text_field :messageable_type, value: "#{current_user.class.name}", type: "hidden" %>
<%= f.submit "Send Reply" %>
<% end %>
<% end %>
Which gives me an error:
First argument in form cannot contain nil or be empty
Extracted source (around line #51):
51 <%= form_for [#conversation, #message] do |f| %>
When I tried to debug it turned out #message = nil inside of _show.html.erb. How is that possible if I defined #message inside of ActiveAdmin controller ?
[EDIT]
In case you're curious, ConversationController below:
class ConversationsController < ApplicationController
before_action :authenticate_user!
def index
#admins = AdminUser.all
#conversations = Conversation.all
end
def new
#conversation = Conversation.new
#conversation.messages.build
end
def create
#conversation = Conversation.create!(conversation_params)
redirect_to conversation_messages_path(#conversation)
end
end
#routes
resources :conversations do
resources :messages
end
Normally you set up instance variables in your controller, and then Rails later does an implicit render of the view once the controller method completes.
However, it is possible to do an explicit render of the view, by calling something like render action: or render template: while the controller method is running, and presumably this is happening within the call to super.
See the Layout and Rendering Rails Guide for more information.
You'll need to move the assignment to be before the call to super.
You may also need to replace #conversation with resource in the ActiveAdmin controller (this is an ActiveAdmin/InheritedResources gem thing).

param is missing or the value is empty: name -what exact value to put in require

I know there is so much docs regarding params.require() and I have indeed tried every possible ways to debug the error but its unavoidable -whatever I put in require, it still gives the same error!
Here is the error,
ActionController::ParameterMissing in UserCrudController#update
Request
Parameters:
{"utf8"=>"✓",
"_method"=>"patch",
"authenticity_token"=>"QknxMRZg8aFEkOatw9uxulLUSiEUXgBFQ4iFbbJHIfgvGCtTq2oI3FI7W8eq5Eqeo3Z3dEPWK2IK2XH96f4faQ==",
"user_crud"=>{"name"=>"Chow", "email"=>"ac#gmail.com"},
"commit"=>"Update User",
"id"=>"4"}
And my Controller section:
class UserCrudController < ApplicationController
def update
# Find a new object using form parameters
#users = UserCrud.find(params[:id])
# Update the object
if #users.update_attributes(users_params)
# If save succeeds, redirect to the show action
flash[:notice] = "UserCrud updated successfully."
redirect_to(user_crud_path(#users))
else
# If save fails, redisplay the form so user can fix problems
render('edit')
end
end
def edit
#users=UserCrud.find(params[:id])
end
def users_params
params.require(:name).permit(:name, :email,:created_at,:updated_at)
end
I am using the model as:
class UserCrud < ApplicationRecord
validates_presence_of :name
validates_presence_of :email
end
Now the View is (edit.html.erb):
<% #page_title = "Update User" %>
<%= link_to("<< Back to List", user_crud_path, :class => 'back-link') %>
<div class="users edit">
<h2>Update User</h2>
<%= form_for(#users) do |f| %>
<%= render(:partial => 'form', :locals => {:f => f}) %>
<div class="form-buttons">
<%= f.submit("Update User") %>
</div>
<% end %>
</div>
Just make following changes in your controller,
def user_params
params.require(:user_crud).permit(:name, :email)
end
As your params contains form inputs under user_crud key.
def user_params
params.require(:user_crud).permit(:name, :email,:created_at,:updated_at)
end
You have to change this code:
def users_params
params.require(:name).permit(
:name, :email,:created_at,:updated_at
)
end
to
private
def user_params
params.require(:user_crud).permit(:name, :email)
end
in your controller.
Just to explain, when setting your "resource_params", requiring a param will search for the root of your json. For example:
def resource_params
params.require(:foo).permit(:bar)
end
Your JSON should looks like this:
{
"foo": {
"bar": 123
}
}

Twilio - phone number verification on Rails 5

I have done a phone number verification via Twilio, but I can't find a way how to implement a feature that sends pin code again (if user didn't received it) but also does it not more that 3 times (so users couldn't keep sending codes over and over again). Also, my code looks a bit anti-pattern, so feel free to suggest a better implementation.
When Devise User registers itself, I send him to create a Profile that belongs_to User. Profile holds all user info (and phone number). Here is the form:
<%= form_for #profile, remote: true do |f| %>
<%= f.label 'Your name' %><br />
<%= f.text_field :first_name, autofocus: true, class: 'form-control' %>
<%= f.label 'Phone number' %><br />
<%= f.text_field :phone, class: 'form-control' %>
</br>
<div id="hideAfterSubmit">
<%= f.submit 'Save', class: 'btn btn-lg btn-primary btn-block' %>
</div>
<% end %>
<div id="verify-pin">
<h3>Enter your PIN</h3>
<%= form_tag profiles_verify_path, remote: true do |f| %>
<div class="form-group">
<%= text_field_tag :pin %>
</div>
<%= submit_tag "Verify PIN", class: "btn btn-primary" %>
<% end %>
</div>
<div id="status-box" class="alert alert-success">
<p id="status-message">Status: Haven’t done anything yet</p>
</div>
#verify-pin and #status-box are display: none. I unhide them with responding create.js.erb.
Create action:
def create
if user_signed_in? && current_user.profile
redirect_to profile_path(current_user), notice: 'Jūs jau sukūrėte paskyrą'
else
#profile = Profile.new(profile_params)
#phone_number = params[:profile][:phone]
#profile.user_id = current_user.id
SmsTool.generate_pin
SmsTool.send_pin(phone_number: #phone_number)
if #profile.save
respond_to do |format|
format.js
end
else
render :new
end
end
end
So at this point profile been created, saved and pin code generated and sent to phone number that user just added.
SmsTool:
def self.generate_pin
##pin = rand(0000..9999).to_s.rjust(4, "0")
puts "#{##pin}, Generated"
end
def self.send_pin(phone_number:)
#client.messages.create(
from: ENV['TWILIO_PHONE_NUMBER'],
to: "+370#{phone_number}",
body: "Your pin is #{##pin}"
)
end
def self.verify(entered_pin)
puts "#{##pin}, pin #{entered_pin} entered"
if ##pin == entered_pin
Current.user.profile.update(verified: true)
else
return
end
end
And Profiles#verify :
def verify
SmsTool.verify(params[:pin])
#profile = current_user.profile
respond_to do |format|
format.js
end
if #profile.verified
redirect_to root_path, notice: 'Account created'
end
end
So what I dont like is SmsTool - as you see I use class variable - couldn't find another way. Also I created a separate Current module just to access Devise current_user object.. :
module Current
thread_mattr_accessor :user
end
ApplicationController:
around_action :set_current_user
def set_current_user
Current.user = current_user
yield
ensure
# to address the thread variable leak issues in Puma/Thin webserver
Current.user = nil
end
And as I mentioned above - I can't find a way how to implement a feature that sends pin code again (if user didn't received it).
And please - feel free to suggest elegant implementations.
p.s. this is my longest post yet. Sorry for that, but I think all info was needed to show you.
UPDATE:
So to resend pin was easy, I just added:
<div id="hiddenUnlessWrongPin">
<%= button_to "Re-send pin", action: "send_pin_again" %>
</div>
and action:
def send_pin_again
#phone_number = current_user.profile.phone
SmsTool.generate_pin
SmsTool.send_pin(phone_number: #phone_number)
end
But I still don't know how to stop sending pin if user already sent three of them. Only way I see is to make new row in db with integer value and increment it every time user sends pin. Is it the only way?
A good starting point would be to look at the Devise::Confirmable module which handles email confirmation. What I really like about it is that it models confirmations as a plain old resource.
I would try something similar but with a seperate model as it makes it really easy to add a time based limit.
class User < ApplicationRecord
has_one :profile
has_many :activations, through: :profiles
end
class Profile < ApplicationRecord
belongs_to :user
has_many :activations
end
# columns:
# - pin [int or string]
# - profile_id [int] - foreign_key
# - confirmed_at [datetime]
class Activation < ApplicationRecord
belongs_to :profile
has_one :user, through: :profile
delegate :phone_number, to: :profile
authenticate :resend_limit, if: :new_record?
authenticate :valid_pin, unless: :new_record?
attr_accessor :response_pin
after_initialize :set_random_pin!, if: :new_record?
def set_random_pin!
self.pin = rand(0000..9999).to_s.rjust(4, "0")
end
def resend_limit
if self.profile.activations.where(created_at: (1.day.ago..Time.now)).count >= 3
errors.add(:base, 'You have reached the maximum allow number of reminders!')
end
end
def valid_pin
unless response_pin.present? && response_pin == pin
errors.add(:response_pin, 'Incorrect pin number')
end
end
def send_sms!
// #todo add logic to send sms
end
end
Feel free to come up with a better name. Additionally this allows you to use plain old rails validations to handle the logic.
You can then CRUD it like any other resource:
devise_scope :user do
resources :activations, only: [:new, :create, :edit, :update]
end
class ActivationsController < ApplicationController
before_action :authenticate_user!
before_action :set_profile
before_action :set_activation, only: [:edit, :update]
# Form to resend a pin notification.
# GET /users/activations/new
def new
#activation = #profile.phone_authentication.new
end
# POST /users/activations/new
def create
#activation = #profile.phone_authentication.new
if #activation.save
#activation.send_sms!
redirect_to edit_user_phone_activations_path(#activation)
else
render :new
end
end
# Renders form where user enters the activation code
# GET /users/activations/:id/edit
def edit
end
# confirms the users entered the correct pin number.
# PATCH /users/activations/:id
def update
if #activation.update(update_params)
# cleans up
#profile.activations.where.not(id: #activation.id).destroy_all
redirect_to profile_path(#profile), success: 'Your account was activated'
else
render :edit
end
end
private
def update_params
params.require(:activation)
.permit(:response_pin)
.merge(confirmed_at: Time.now)
end
def set_profile
#profile = current_user.profile
end
def set_activation
#profile.activations.find(params[:id])
end
end
app/views/activations/new.html.erb:
<%= form_for(#activation) do |f| %>
<%= f.submit("Send activation to #{#activation.phone_number}") %>
<% end %>
No activation SMS? <%= link_ to "Resend", new_user_activation_path %>
app/views/activations/edit.html.erb:
<%= form_for(#activation) do |f| %>
<%= f.text_field :response_pin %>
<%= f.submit("Confirm") %>
<% end %>

Rails Form Object with Virtus: has_many association

I am having a tough time figuring out how to make a form_object that creates multiple associated objects for a has_many association with the virtus gem.
Below is a contrived example where a form object might be overkill, but it does show the issue I am having:
Lets say there is a user_form object that creates a user record, and then a couple associated user_email records. Here are the models:
# models/user.rb
class User < ApplicationRecord
has_many :user_emails
end
# models/user_email.rb
class UserEmail < ApplicationRecord
belongs_to :user
end
I proceed to create a a form object to represent the user form:
# app/forms/user_form.rb
class UserForm
include ActiveModel::Model
include Virtus.model
attribute :name, String
attribute :emails, Array[EmailForm]
validates :name, presence: true
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
puts "The Form is VALID!"
puts "I would proceed to create all the necessary objects by hand"
# user = User.create(name: name)
# emails.each do |email_form|
# UserEmail.create(user: user, email: email_form.email_text)
# end
end
end
One will notice in the UserForm class that I have the attribute :emails, Array[EmailForm]. This is an attempt to validate and capture the data that will be persisted for the associated user_email records. Here is the Embedded Value form for a user_email record:
# app/forms/email_form.rb
# Note: this form is an "Embedded Value" Form Utilized in user_form.rb
class EmailForm
include ActiveModel::Model
include Virtus.model
attribute :email_text, String
validates :email_text, presence: true
end
Now I will go ahead and show the users_controller which sets up the user_form.
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user_form = UserForm.new
#user_form.emails = [EmailForm.new, EmailForm.new, EmailForm.new]
end
def create
#user_form = UserForm.new(user_form_params)
if #user_form.save
redirect_to #user, notice: 'User was successfully created.'
else
render :new
end
end
private
def user_form_params
params.require(:user_form).permit(:name, {emails: [:email_text]})
end
end
The new.html.erb:
<h1>New User</h1>
<%= render 'form', user_form: #user_form %>
And the _form.html.erb:
<%= form_for(user_form, url: users_path) do |f| %>
<% if user_form.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(user_form.errors.count, "error") %> prohibited this User from being saved:</h2>
<ul>
<% user_form.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<% unique_index = 0 %>
<% f.object.emails.each do |email| %>
<%= label_tag "user_form[emails][#{unique_index}][email_text]","Email" %>
<%= text_field_tag "user_form[emails][#{unique_index}][email_text]" %>
<% unique_index += 1 %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Note: If there is an easier, more conventional way to display the inputs for the user_emails in this form object: let me know. I could not get fields_for to work. As shown above: I had to write out the name attributes by hand.
The good news is that the form does render:
The html of the form looks ok to me:
When the above input is submitted: Here is the params hash:
Parameters: {"utf8"=>"✓", "authenticity_token"=>”abc123==", "user_form"=>{"name"=>"neil", "emails"=>{"0"=>{"email_text"=>"foofoo"}, "1"=>{"email_text"=>"bazzbazz"}, "2"=>{"email_text"=>""}}}, "commit"=>"Create User form"}
The params hash looks ok to me.
In the logs I get two deprecation warnings which makes me think that virtus might be outdated and thus no longer a working solution for form objects in rails:
DEPRECATION WARNING: Method to_hash is deprecated and will be removed in Rails 5.1, as ActionController::Parameters no longer inherits from hash. Using this deprecated behavior exposes potential security problems. If you continue to use this method you may be creating a security vulnerability in your app that can be exploited. Instead, consider using one of these documented methods which are not deprecated: http://api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html (called from new at (pry):1)
DEPRECATION WARNING: Method to_a is deprecated and will be removed in Rails 5.1, as ActionController::Parameters no longer inherits from hash. Using this deprecated behavior exposes potential security problems. If you continue to use this method you may be creating a security vulnerability in your app that can be exploited. Instead, consider using one of these documented methods which are not deprecated: http://api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html (called from new at (pry):1)
NoMethodError: Expected ["0", "foofoo"} permitted: true>] to respond to #to_hash
from /Users/neillocal/.rvm/gems/ruby-2.3.1/gems/virtus-1.0.5/lib/virtus/attribute_set.rb:196:in `coerce'
And then the whole thing errors out with the following message:
Expected ["0", <ActionController::Parameters {"email_text"=>"foofoo"} permitted: true>] to respond to #to_hash
I feel like I am either close and am missing something small in order for it to work, or I am realizing that virtus is outdated and no longer usable (via the deprecation warnings).
Resources I looked at:
this article.
this video
I did attempt to get the same form to work but with the reform-rails gem. I ran into an issue there too. That question is posted here.
Thanks in advance!
I would just set the emails_attributes from user_form_params in the user_form.rb as a setter method. That way you don't have to customize the form fields.
Complete Answer:
Models:
#app/modeles/user.rb
class User < ApplicationRecord
has_many :user_emails
end
#app/modeles/user_email.rb
class UserEmail < ApplicationRecord
# contains the attribute: #email
belongs_to :user
end
Form Objects:
# app/forms/user_form.rb
class UserForm
include ActiveModel::Model
include Virtus.model
attribute :name, String
validates :name, presence: true
validate :all_emails_valid
attr_accessor :emails
def emails_attributes=(attributes)
#emails ||= []
attributes.each do |_int, email_params|
email = EmailForm.new(email_params)
#emails.push(email)
end
end
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
user = User.new(name: name)
new_emails = emails.map do |email|
UserEmail.new(email: email.email_text)
end
user.user_emails = new_emails
user.save!
end
def all_emails_valid
emails.each do |email_form|
errors.add(:base, "Email Must Be Present") unless email_form.valid?
end
throw(:abort) if errors.any?
end
end
# app/forms/email_form.rb
# "Embedded Value" Form Object. Utilized within the user_form object.
class EmailForm
include ActiveModel::Model
include Virtus.model
attribute :email_text, String
validates :email_text, presence: true
end
Controller:
# app/users_controller.rb
class UsersController < ApplicationController
def index
#users = User.all
end
def new
#user_form = UserForm.new
#user_form.emails = [EmailForm.new, EmailForm.new, EmailForm.new]
end
def create
#user_form = UserForm.new(user_form_params)
if #user_form.save
redirect_to users_path, notice: 'User was successfully created.'
else
render :new
end
end
private
def user_form_params
params.require(:user_form).permit(:name, {emails_attributes: [:email_text]})
end
end
Views:
#app/views/users/new.html.erb
<h1>New User</h1>
<%= render 'form', user_form: #user_form %>
#app/views/users/_form.html.erb
<%= form_for(user_form, url: users_path) do |f| %>
<% if user_form.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(user_form.errors.count, "error") %> prohibited this User from being saved:</h2>
<ul>
<% user_form.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<%= f.fields_for :emails do |email_form| %>
<div class="field">
<%= email_form.label :email_text %>
<%= email_form.text_field :email_text %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
You have an issue because you haven't whitelisted any attributes under :emails. This is confusing, but this wonderful tip from Pat Shaughnessy should help set you straight.
This is what you're looking for, though:
params.require(:user_form).permit(:name, { emails: [:email_text, :id] })
Note the id attribute: it's important for updating the records. You'll need to be sure you account for that case in your form objects.
If all this form object malarkey with Virtus gets to be too much, consider Reform. It has a similar approach, but its raison d'etre is decoupling forms from models.
You also have an issue with your form… I'm not sure what you were hoping to achieve with the syntax you're using, but if you look at your HTML you'll see that your input names aren't going to pan out. Try something more traditional instead:
<%= f.fields_for :emails do |ff| %>
<%= ff.text_field :email_text %>
<% end %>
With this you'll get names like user_form[emails][][email_text], which Rails will conveniently slice and dice into something like this:
user_form: {
emails: [
{ email_text: '...', id: '...' },
{ ... }
]
}
Which you can whitelist with the above solution.
The problem is that the format of the JSON being passed to UserForm.new() is not what is expected.
The JSON that you are passing to it, in the user_form_params variable, currently has this format:
{
"name":"testform",
"emails":{
"0":{
"email_text":"email1#test.com"
},
"1":{
"email_text":"email2#test.com"
},
"2":{
"email_text":"email3#test.com"
}
}
}
UserForm.new() is actually expecting the data in this format:
{
"name":"testform",
"emails":[
{"email_text":"email1#test.com"},
{"email_text":"email2#test.com"},
{"email_text":"email3#test.com"}
}
}
You need to change the format of the JSON, before passing it to UserForm.new(). If you change your create method to the following, you won't see that error anymore.
def create
emails = []
user_form_params[:emails].each_with_index do |email, i|
emails.push({"email_text": email[1][:email_text]})
end
#user_form = UserForm.new(name: user_form_params[:name], emails: emails)
if #user_form.save
redirect_to #user, notice: 'User was successfully created.'
else
render :new
end
end

Resources