In my Rails 3.2 project, I have a form to create a new post in new.html.erb in app/views/posts/
<%= form_for(#post) do |post_form| %>
...
<div class="field">
<%= post_form.label :title %><br />
<%= post_form.text_field :title %>
</div>
<div class="field">
<%= post_form.label :content %><br />
<%= post_form.text_field :content %>
</div>
<div class="actions">
<%= post_form.submit %>
</div>
<% end %>
Then the create function in posts_controller.rb
def create
#post = Post.new(params[:post])
if #post.save
format.html { redirect_to #post }
else
format.html { render action: "new" }
end
end
When the user submits a post, the title and content of the post are added to the Post model. However, I also want to add to another field of that post. For the field random_hash (which the user doesn't get to specify), I want to make it a string of 8 lowercase letters, the first 2 of which are the first 2 letters of the title, and the last 6 are random lowercase letters. How can I do that?
def create
#post = Post.new(params[:post])
#post.random_hash = generate_random_hash(params[:post][:title])
if #post.save
format.html { redirect_to #post }
else
format.html { render action: "new" }
end
end
def generate_random_hash(title)
first_two_letters = title[0..1]
next_six_letters = (0...6).map{65.+(rand(25)).chr}.join
(first_two_letters + next_six_letters).downcase
end
Put that in your controller. You obviously have to have random_hash attribute for Post model to work.
I am using Kent Fredric's solution to generate six random letters.
Related
One Article has_many Images. When creating a new Article, Users can add 2 images max.
In my controller I run "build" for images only twice, but when I submit the form that has 3 image fields, it succeeds. Is there any need to run "build" at all? It seems pointless in this scenario, is there another way to better ensure only 2 images are accepted?
articles_controller.rb
def new
#article = Article.new
2.times { #article.images.build }
end
Note the "2.times" here.
def create
#article = Article.new(place_params)
#article.user = current_user
respond_to do |format|
if #review.save
params[:images][:image_file].each do |image_params|
#image = #article.images.create(image_file: image_params, user: current_user)
end
end
end
end
_form.html.erb
<%= form_with(model: article, url: create_article_path(#article), local: true) do |form| %>
<div class="field">
<%= form.label :title %>
<%= form.text_area :title %>
</div>
<%= form.fields_for :images, #image do |image| %>
<div class="field">
<%= image.label :image_file_1, "Image 1" %>
<%= photo.file_field :image_file, name: "images[image_file][]", id: :article_images_image_file_1 %>
</div>
<div class="field">
<%= image.label :image_file_2, "Image 2" %>
<%= photo.file_field :image_file, name: "images[image_file][]", id: :article_images_image_file_2 %>
</div>
<div class="field">
<%= image.label :image_file_3, "Image 3" %>
<%= photo.file_field :image_file, name: "images[image_file][]", id: :article_images_image_file_3 %>
</div>
<% end %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
SUCCESS (But why?)
In short -- Your build statement is prepping the view to have 2 child objects. But you're manually creating them, so you're rendering the build statement as useless. You don't have to do it this way, you can declare nested attributes in the model, then whitelist in the controller, then auto-add them in the view. (see code example below)
Build itself does change how many objects are instantiated, but you're overriding that.
You are also manually saving the images, which you do not have to do. There's a bit of rails magic that saves all the children for you, if you've built them properly.
CodeView
1 The Model
app/models/article.rb
class Article < ApplicationRecord
has_many :images
validates :images, length: {maximum: 2}
accepts_nested_attributes_for :images
end
2 bits of note here. Firstly, in your validation, only allow 2 object, if you try to save a third, it will fail. Secondly, accepting the attribute in the model allows you to create safe params in the controller, thus alleviating your need to manually create. (unless of course, you really want to)
2 The View
<%= form_with(model: article, url: article_path(#article), local: true) do |form| %>
<div class="field">
<%= form.label :title %>
<%= form.text_area :title %>
</div>
<%= form.fields_for :images do |image_form| %>
<div class="field">
<%= image_form.label "image_file_#{image_form.index + 1}" %>
<%= image_form.file_field :image_file %>
<%= image_form.hidden_field :user_id, value: current_user.id %>
</div>
<% end %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
The change here is a) I added the user directly to the form b.) because you are accepting attribute in the model and we'll whitelist the attribute in the controller, you don't need to pass an object to the field_for -- :images will do just fine. And because you will say to build it twice in your controller, you'll have 2 image objects in the form. Additionally, because you wanted a label of Image 1 and Image 2, with fields_for you automatically get access to the index of the object (just like you'd have with any array) by calling object.index.
3 The Controller - New Action
app/models/article.rb
Your action works perfectly well, keep it just as it is.
def new
#article = Article.new
2.times { #article.images.build }
end
4 The Controller - Strong Params
def article_params
params.require(:article).permit(:title, :body, images_attributes: [:id, :article_id,:user_id, :image_file])
end
Whitelisting your params altogether will save time and it's easier to read than permitting them in each controller, though you CAN do that if you need to, for instance if params are allowed in certain actions but not in others.
5 The Controller - Create Action
def create
#article = Article.new(article_params)
respond_to do |format|
if #article.save
format.html { redirect_to #article, notice: 'Article was successfully created.' }
format.json { render :show, status: :created, location: #article }
else
format.html { render :new }
format.json { render json: #article.errors, status: :unprocessable_entity }
end
end
end
This will probably look similar if not identical to the default create action in a scaffold, and this is all you need. The child image objects will not be created unless the parent can be created, so you don't have to worry about adding them in an if save.
I'm trying to pass some params from a form to a view but all I get is param is missing or the value is empty: quotes. I've checked the database and the input is saved there but for some reason the data becomes nil somewhere along the way to the view.
I'm passing along the :quotes parameter from the view to the controller, and that should be it, shouldn't it?
quotes_controller.rb
class QuotesController < ApplicationController
def new
end
def create
#quote = Quote.new(quote_params)
#quote.save
redirect_to #quote
end
def show
#quote = Quote.find(quote_params[:id])
end
private
def quote_params
params.require(:quotes).permit(:title, :text)
end
end
new.html.erb
<h2>Add Quote</h2>
<%= form_for :quotes, url: quotes_path do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
show.html.erb
<h2>Saved Quotes</h2>
<p>
<strong>Title:</strong>
<%= #quote.title %>
</p>
<p>
<strong>Text:</strong>
<%= #quote.text %>
</p>
<% end %>
I'm using the Rails Dev Box if that makes any difference.
Since you mentioned the records are indeed getting saved to the database, new and create actions should not be a problem. However, when you do redirect_to #quote the id of #quote is available as params[:id] in show. So I think, modifying the show action in controller as below should work.
def show
#quote = Quote.find(params[:id])
end
On another note, you should consider modifying your create action to account for submissions for new quotes that don't pass the validations or don't get saved to the database.
def create
#quote = Quote.new(quote_params)
if #quote.save
flash[:success] = "Successfully created the new quote..."
redirect_to #quote
else
render 'new'
end
end
This would result in a friendly flash message to the user on the redirected page if the quote gets created. If not, it renders quotes#new to try another submission.
The bug is the view for new action. You don't have anywhere quotes instance variables set. Actually there should be quote there, but there is not.
Add in new:
#quote = Quote.new
then use:
form_for(#quote)
in your new view.
I'm trying to add an event registration to a current or new order. Question at the end of the post.
Event model: Contains the basic event information like title, date, description. This event model has many event options.
Event Option: Contains a description and a price. This event option has many registrations.
Registration: Allows the user to register and it takes the price from the event option price. This registration belong to an event option and to the order model.
Order: Calculates the total of the order based on the sum of all the registrations associated with it.
Creating a new registration:
In the event option show page, I have a form that creates a new registration using remote: true.
Here's the form:
<%= form_for(#registration, remote: true) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :lastname %><br>
<%= f.text_field :lastname %>
</div>
<div class="field">
<%= f.label :event_option_id %><br>
<%= f.text_field :event_option_id, value: #event_option.id %>
</div>
<div class="field">
<%= f.label :order_item_id %><br>
<%= f.text_field :order_item_id%>
</div>
<div class="field">
<%= f.label :price %><br>
<%= f.text_field :price, value: #event_option.price%>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
When the form is submitted, it creates the event and also creates a new order if the order does not exist. To create the new order I have this helper method in the application controller:
def current_order
if !session[:order_id].nil?
Order.find(session[:order_id])
else
Order.new
end
end
Here's the create method in the registrations controller:
def create
#order = current_order
#registration = Registration.new(registration_params)
#order_id = current_order.id
respond_to do |format|
if #registration.save
format.html { redirect_to #registration, notice: 'Registration was successfully created.' }
format.json { render :show, status: :created, location: #registration }
format.js {}
#order.save
session[:order_id] = #order.id
else
format.html { render :new }
format.json { render json: #registration.errors, status: :unprocessable_entity }
end
end
end
The problem is that I'm not able add the registration to the order. I'm guessing that this is happening because the registration is created before the order. The last two line of the if #registration.save in the respond_to block are saving the order. How can I add the registration to the order? Can both, the new registration and new order be created at the same time?
A simple way to get around this is to assign the registration to the order before saving the order...
#order.registrations << #registration
#order.save
Alternatively you could create the association at the time you're creating the #registration record.
Instead of...
#registration = Registration.new(registration_params)
do....
#registration = #order.registrations.build(registration_params)
I making a very simple RESTful app and when I input a string into the form field and submit it, the database hold NULL instead of the input string.
Here is my controller:
def create
#song = Song.create(params[:title])
flash[:success] = "You have successfully created a new project!"
redirect_to "/songs/#{#song.id}"
end
Here is my form in the new.html.erb file:
<%= form_for(#song) do |f| %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<br>
<% end %>
If you are using rails < 4 then you should have
def create
#song = Song.create(params[:song])
flash[:success] = "You have successfully created a new project!"
redirect_to #song
end
and if you are using rails > 4 then you should have
def create
#song = Song.create(song_params)
flash[:success] = "You have successfully created a new project!"
redirect_to #song
end
private
def song_params
params.require(:song).permit(:title)
end
I have an Opportunity model that has Activity as a nested resource. on my opportunities/show page, I have a list of activities for that opportunity and a form to add new activities. When I click "add activity" I get:
undefined method `activities' for nil:NilClass
Here is the error source:
# POST /activities.json
def create
#activity = #opportunity.activities.new(activity_params)
if #activity.save
redirect_to #opportunity, notice: 'Activity has been added'
else
I defined my Opportunity model as having many Activities and that my Activities belongs to an Opportunity. Here are the relevant parts of my Activity controller:
def create
#activity = #opportunity.activities.new(activity_params)
if #activity.save
redirect_to #opportunity, notice: 'Activity has been added'
else
redirect_to #opportunity, alert: 'Unable to add Activty'
end
end
And here is my views/activities/new code
<%= form_for ([#opportunity, #opportunity.activities.new]) do |f| %>
<div class="field">
<%= f.label "Date Assigned" %> <br />
<%= f.text_field :date_assigned %>
</div>
<div class="field">
<%= f.label "Date Due" %> <br />
<%= f.text_field :date_due %>
</div>
<div class="field">
<%= f.label "Description" %> <br />
<%= f.text_field :description %>
</div>
<div class="field">
<%= f.label "Status" %> <br />
<%= f.text_field :status %>
</div>
<div class="actions">
<%= f.submit 'Add' %>
</div>
<% end %>
My routes:
resources :opportunities do
resources :activities
end
thank you!!
Your #opportunity is undefined(nil) in the block.
You must get #opportunity prior to building activities on it as :
#opportunity = Opportunity.find(params[:opportunity_id])
(Reason for :opportunity_id : Since this is ActivityController and your model is nested, by conventional nested RESTful resources (as specified in your routes), the parameter is automatically assigned as model_id => opportunity_id)
Changed code:
def create
#opportunity = Opportunity.find(params[:opportunity_id])
#activity = #opportunity.activities.new(activity_params)
if #activity.save
redirect_to #opportunity, notice: 'Activity has been added'
else
Also, it is recommend to use build instead of new while building object for relations.
Try using build instead of new.
#activities = #oportunities.activities.build(activity_params)
That should work
Edit:
You didn't find for #oportunities before the build :P
def create
#oportunities.find(params[:id])
#activity = #opportunity.activities.new(activity_params)
if #activity.save
redirect_to #opportunity, notice: 'Activity has been added'
else
redirect_to #opportunity, alert: 'Unable to add Activty'
end
end