Rails 4, add dynamic fields without nested attributes - ruby-on-rails

I'm currently sitting with a problem where I am trying to add dynamic fields, and it works, however I need to created a second model for the nested attributes that are dynamically generated on the field.
I have a very easy and simple form which asks for a user's name, I want to be able to allow the user to click a button ("Add additional name") and have that field added dynamically then add another name. Can I do this using only the one model and controller, and without using nested forms?
This is what I currently have, but I do not want it working this way:
Controller
class GuestsController < ApplicationController
skip_before_filter :authenticate_user!, only: [:new, :create]
def index
#guest = Guest.all
end
def new
guest = Guest.new
#guest_form = GuestForm.new(guest)
end
def show
#guest = Guest.find(params[:id])
end
def create
guest = Guest.new
#guest_form = GuestForm.new(guest)
#guest_form.submit(guest_params)
if #guest_form.save
respond_to do |format|
format.html { redirect_to :back, notice: 'Thank you for replying' }
format.js
end
else
respond_to do |format|
format.html { render :new }
format.js
end
end
end
def destroy
#guest = Guest.find(params[:id])
#guest.destroy
redirect_to guests_path
end
private
def guest_params
params.require(:guest).permit(:status, :name, :message, plusones_attributes: [:id, :name, :oldness, :_destroy])
end
end
Models:
class Guest < ActiveRecord::Base
has_many :plusones, dependent: :destroy
belongs_to :user
end
class Plusone < ActiveRecord::Base
belongs_to :guest
end
So ideally I would like to use one Model, and allow additional fields to be entered of the exact same attribute, meaning if I have a Name: field, I should be able to click "Add another name" and add that name, and that would be saved as an individual guest in the table, meaning the first guest would have guest_id of 1, and the dynamically added field for another guest would add a guest with guest_id of 2.

I think you are making a mistake in thinking that there must be a one to one relationship between controllers and models. Have a look at ActiveRecord's Nested Attributes. That shows that you can have a single form submit data to an object and it's sub-objects in one action.
Have a look at fields_for, for information about how to configure a form to allow you to enter data for sub-objects.
When I've worked with nested set, I've found using a JavaScript tool like dynatree is the best way to display the nested data to users. I've also created a gem TreeDecorator, to make it easy to render nested sets as nested HTML lists, that can then be passed to dynatree.

I managed to find a solution that allowed me to keep my nested attributes and still be able to display the related child elements in the index page.
This is what I did to get the plusones related to the guest_id to display within index.html.erb
<% #guest.each do |guest| %>
<% guest.plusones.each do |plus| %>
<%= plus.name %>
<%= plus.oldness %>
<% end %>
<% end %>

Related

How can I modify a variable in the controller from the view (ruby on rails)

I am making a portfolio page in rails. On the front page I have an "About" section where I have a personal description of myself. I want to be able to change this dynamically (not hard-coded html).
I would like to have the description be a text variable or string that I can modify through a form in the view section.
Questions
1. How should I declare this variable in the controller?
2. How do I access and change it from the form in the view?
3. Is there a better solution to my problem?
The only way to do this is to send the updated values to your controller. You need to have a form on your portfolio page:
#config/routes.rb
resources :users do
resources :portfolios #-> url.com/users/:user_id/portfolios/:id
end
#app/controllers/portfolios_controller.rb
class PortfoliosController < ApplicationController
def show
#user = User.find params[:user_id]
end
end
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def update
#user = User.find params[:id]
#user.update user_params
end
private
def user_params
params.require(:user).permit(:about)
end
end
#app/views/portfolios/show.html.erb
<%= form_for #user do |f| %>
<%= f.text_field :about %>
<%= f.submit %>
<% end %>
Without any more context, that's the best I can give.
You will need to connect a database to store and retrieve dynamic stuff. You can access a variable in views if you define it with # like;
#about= Me.last.about
where Me could be your model that the information is saved in and Me.last would be the instance of that model. You can update this information by updating the model like
Me.last.update_attributes :about=> params[:about]
where params[:about] would be the params from the field in the form.
I would recommend following a guide so you get a complete solution. Below I have the major steps a user would take to update some content. This code is not a complete solution.
Your form would submit the updated content to another controller action (through another route).
<%= form_for #user do |f| %>
In that controller, you would store the content (usually in the database).
def update
#user = User.find(params[:id])
#user.update(user_params)
redirect_to :back
end
The original controller action would have a variable that got the content from the place you stored it
def show
#user = User.find(params[:id])
end

Ruby on rails and coupon model

I have really been scratching my head on this and would greatly appreciate help. I have a store setup where people can take courses. I have a course model, order model, and coupon model. Here are the associations in the models
class Course < ActiveRecord::Base
belongs_to :category
has_many :orders
has_many :coupons
end
class Order < ActiveRecord::Base
belongs_to :course
belongs_to :user
belongs_to :coupon
end
class Coupon < ActiveRecord::Base
belongs_to :course
has_many :orders
end
I have a very simple coupon model setup that has code and newprice columns. I want the ability for someone to be able to fill out the coupon form on the new order page and it to update the price.
In my my view for new order I have two forms one for the new order and one for the coupon. How do check in my controller if a user has entered the correct coupon code? How do I update the coupon price to be shown instead of the course price?
here is my order controller
class OrdersController < ApplicationController
before_action :set_order, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
def index
#orders = Order.all
end
def show
end
def new
course = Course.find(params[:course_id])
#course = Course.find(params[:course_id])
#order = course.orders.build
#coupon = Coupon.new
#user = current_user.id
#useremail = current_user.email
end
def discount
course = Course.find(params[:course_id])
#order = course.orders.build
#user = current_user.id
#useremail = current_user.email
end
def edit
end
def create
#order = current_user.orders.build(order_params)
if current_user.stripe_customer_id.present?
if #order.pay_with_current_card
redirect_to #order.course, notice: 'You have successfully purchased the course'
else
render action: 'new'
end
else
if #order.save_with_payment
redirect_to #order.course, notice: 'You have successfully purchased the course'
else
render action: 'new'
end
end
end
def update
if #order.update(order_params)
redirect_to #order, notice: 'Order was successfully updated.'
else
render action: 'edit'
end
end
def destroy
#order.destroy
redirect_to orders_url
end
private
# Use callbacks to share common setup or constraints between actions.
def set_order
#order = Order.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def order_params
params.require(:order).permit(:course_id, :user_id, :stripe_card_token, :email)
end
end
You can accomplish this with an AJAX request using the form_for helper with the :remote option.
Summary
Set :remote option to true for your coupons form to submit the AJAX request.
Create controller action to handle the AJAX request from the form.
Use JavaScript to respond to the controller action to update your orders form (the other form in your view) with the new price information, etc.
AJAX request using `:remote`
Here's some example code representing your coupon form :
<%= form_for #coupon, method: :post, url: check_coupon_code_path, remote: true do |f| %>
<%= f.text_field :coupon_code, :placeholder => "Enter your coupon" %>
<%= f.submit "Submit Coupon Code" %>
<% end %>
Notice the following:
The :remote option for the form_for tag is set to true.
The :url option is the path to your controller action in your CouponsController. Because the :remote option is set to true, the request will be posted to this :url option as an AJAX request.
In this code example, it's assuming it has a route defined like this in the routes.rb file to handle the AJAX request for checking the coupon code:
post 'check_coupon_code' => 'coupons#check_coupon_code'
Note: In the forms_for helper, the :url option appends _path to the prefix defined in the routes.rb file.
Bonus note: Use the command rake routes to see the available routes and their respective controller action targets.
Handle AJAX request in the Controller
In your CouponsController, define the action check_coupon_code to handle your AJAX request from the above form_for:
def check_coupon_code
# logic to check for coupon code here
respond_to do |format|
if # coupon code is valid
format.js {}
else
# some error here
end
end
end
Notice the format.js in the respond_to block of the action. This allows the controller to respond to the AJAX request with JavaScript to update your orders form in your view. You'll have to define a corresponding app/views/coupons/check_coupon_code.js.erb view file that generates the actual JavaScript code that will be sent and executed on the client side (or name the JavaScript file check_coupon_code.js.coffee if you're using CoffeeScript).
Updating with JavaScript
The JavaScript in your check_coupon_code.js.erb file will then update the price in your order form.
WARNING: Even if you use JavaScript to change the order price on the client-side (i.e. the browser), it is critical to validate the actual price again in the back-end (i.e. in your controller) in case some malicious user tries to manipulate the browser's request, etc.
You can see the official RailsGuide for another example.

How to add current_user to user_id to form_for in rails?

I have a form for creating materials (title, description and content - all basic). The form saves these details just fine but it doesn't save the user_id, which should be the user_id of the current_user. How do I do this? It must be easy but nothing has worked so far.
def create
#material = Material.new(params[:material])
if #material.save
flash[:success] = "Content Successfully Created"
redirect_to #material
else
render 'new'
end
end
def create
#material = Material.new(params[:material])
#material.user_id = current_user.id if current_user
if #material.save
flash[:success] = "Content Successfully Created"
redirect_to #material
else
render 'new'
end
end
There are a few different ways to do it depending on how you have your application setup. If there is a relationship between the user and materials (User has many materials), you could use that in your controller:
def create
#material = current_user.materials.new(params[:material])
# ...
end
If you don't have that relationship, I would still recommend setting it in the controller as opposed to a hidden field in the form. This will be more secure because it won't let someone tamper with the user id value:
def create
#material = Material.new(params[:material].merge(user_id: current_user))
# ...
end
Assuming you are saving the login users's object in the current_user following will work for you
#material = Material.new(params[:material])
#material.user_id = current_user.id
if #material.save
With Rails 5 and parameters needing to be permitted before objects are created, this is the simplest way to merge the current_user into the params, kudos to #Peter Brown in his answer:
def create
#discussion = current_user.materials.new(new_material_params)
# ...
end
private
def new_material_params
params.require(:material).permit(:title, :description,: content)
end
If you have nested object creation using accepts_nested_attributes_for, you need to manually merge deep into the association parameters:
class User < ApplicationRecord
has_many :discussions # Used to associate User with Discussion later
end
class Comment < ApplicationRecord
belongs_to :user
end
class Discussion < ApplicationRecord
belongs_to :user
has_many :comments
accepts_nested_attributes_for :comments
end
class DiscussionsController < ApplicationController
def create
# Merge the params[:discussion][:user_id] by using the relationship's #new
#discussion = current_user.discussion.new(new_discussion_params)
end
private
# Sanitized params for creation, not editing
def new_discussion_params
params.require(:discussion)
.permit(:title, :user_id,
comments_attributes: [:id, :content, :discussion_id, :user_id])
.tap do |discussion_params|
# Require the association parameters, and if they exist,
# set :user_id for each.
discussion_params.require(:comments_attributes).each do |i, comment|
comment.merge!(user_id: current_user.id)
end
end
end
end
Heads up: Setting (or overwriting!) what will be params[:discussion][:comments_attributes]["0"][:user_id] works fine for creation. But if you allow editing deep hierarchies in addition to creation, make sure you don't accidentally overwrite all the :user_ids with the current user.

Create Child only and select a Parent in nested form?

Instead of creating a new Parent and also creating the children. Is it possible to select from a list of Parents and then only create the children that are assigned to a current user and that specific Survey?
Lets use this example:
class Survey < ActiveRecord::Base
has_many :questions
accepts_nested_attributes_for :questions
end
class Question < ActiveRecord::Base
belongs_to :survey
belongs_to :user
end
And then in the controller:
def new
# #survey = select menu of all Surveys
3.times do
question = #survey.questions.build
end
end
def create
# Saves new questions with current user
if #survey.save
flash[:notice] = "Success"
redirect_to #survey
else
render :action => 'new'
end
end
I'm not sure what the create and new actions would turn into. Any idea?
You can call the edit action on a existing survey passing the selected survey to it:
edit_survey_path(#survey)
Then you can load the selected survey in that action:
def edit
#survey = Survey.find(params[:id])
end
In the edit view, use a nested form to add/delete questions, and then, in the update action, updating your surveys attributes will also add and delete the questions.
def update
#survey = Survey.find(params[:id])
#survey.update_attributes(params[:survey])
redirect_to ...
end
All of this will work assuming you've set accepts_nested_attributes_for :questions in the survey model.
My answer here is a summary of Ryan Bates' screencast on nested forms which I think you've already seen, based on the similarity of your example and his.
What I'd like to point out here is that you can achieve what you want using exactly the same code, however using the edit/update actions on your parent model instead of new/create on the child model.
Edit:
In order to assign the current user to a survey question, do the explicit assignment in the new and edit action:
def new
#survey = Survey.new
3.times do
question = #survey.questions.build(:user_id => current_user.id)
end
end
def edit
# find the preselected Survey...
#survey = Survey.find(params[:id])
# This adds a (one) new empty question, consider doing it via Javascript
# for adding multiple questions.
#survey.questions.build(:user_id => current_user.id)
end
In your form for questions, add:
<%= form_builder.hidden_field :user_id %>
Don't forget to replace form_builder with your actual form builder object.
Now all the new questions will be assigned to the current user because the current user was submitted by the form along with the other attributes for questions.

How do I access a Profile model insider users_controller in ruby on rails?

I would like to access "profiles" table from my Profile model which belongs_to :user "User model".
Firstly in my show action of the users_controller I'd like to grab data from the profiles table in order to show on the users profile page.
Secondly I'd like to make it possible for users to edit these things using a form. I know this is done in the update action? while edit action makes it possible to show a form on the edit view page..
Here is my controller:
class UsersController < ApplicationController
def new
#user = User.new
#title = "Practice"
end
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
#user.build_profile.save #same as Profile.new(:user_id => #user.id)
login #user
UserMailer.join_confirmation(#user).deliver
format.js { render :js => "window.location = '#{root_path}'" }
flash[:notice] = "Welcome!"
else
format.js { render :form_errors }
end
end
end
def show
end
def update
end
def edit
end
end
1) How would I access my profiles table?
The User has_one :profile
The Profile belongs_to :user
Advice will be appreciated. Took me half of the day to figure out how to have a row corresponding to a newly created user at sign up created in the profiles table and now the next step is to be able to grab data from the model in my users_controller. i know I could just create a profiles_controller and do things there but I don't want to attempt that right now as I'm sure there's a way to do it via the users_controller.
Thanks in advance for advice given.
View:
<%= #profile_data.first_name %>
<h4><%= current_user.username.capitalize %></h4>
<%= gravatar_image_tag(current_user.email, :alt => #title, :class => "gravatar", :gravatar => { :size => 150 }) %>
<br />
Trying to pull first_name from profiles table through users_controller
def show
#user = User.find_by_username(params[:username])
#profile_data = #user.profile
end
route:
match ':username' => "users#show"
I expect to see the name stored in first_name column of the profiles table when i visit localhost:3000/username
it doesn't show the users first name.
You can just do:
#user.profile
That will return the profile belonging to the User
Otherwise:
Profile.where('whatever condition you fancy')
will return A profile object based on conditions
You want a has_one:profile on the User model, and a belongs_to:user on the Profile model, making sure you have the appropriate user_id column in the profiles table. Add accepts_nested_attributes_for:profile to the User model and then you can create/edit the user's profile in the same form for editing users.
Read up on the rails release notes on nested attributes and guides: associations and forms.

Resources