Creating multiple children at the same time as I create the parent - ruby-on-rails

I'm working on a simple rails task list app for learning purposes, and one of the things I would like to have on the app is to be able to create a new list at the same time as I can add in the tasks within that list. I have finalized the basic CRUD actions for creating lists, and now I want to add the capability for creating tasks at the same time as the creation of lists.
I have done some of the initial associations like so:
My List model:
class List < ApplicationRecord
has_many :tasks
accepts_nested_attributes_for :tasks
end
My Task model:
class Task < ApplicationRecord
belongs_to :list
end
Also I've changed my list_params to return the tasks aswell:
def list_params
params.require(:list).permit(:title, :public, task_attributes: [:text])
end
Now my problem is with how to write the form for my list with the possibility to add a dynamic number of tasks within it, then send those tasks over to my create action in order to save it.
My new action is as simple as it gets:
def new
#list = List.new
end
My current form is like so:
<%= form_with scope: :list, url: lists_path, local: true do |form| %>
<p>
<%= form.label :title %><br>
<%= form.text_field :title %>
</p>
<p>
<%= form.label :public %><br>
<%= form.check_box :public %>
</p>
<h2>Tasks</h2>
<%= form.fields_for :tasks do |task_form| %>
<p>
<%= task_form.label :text %><br>
<%= task_form.text_field :text %><br>
</p>
<% end %>
<p>
<%= form.submit %>
</p>
<% end %>
I intend to use this for testing purposes, to first create a list with one task, then one with two tasks, and then finally create some code to be able to add new fields via javascript so I can create an indefinite number of tasks. The problem I am arriving at however, is that when I submit this form, and call params at my create action, I can see it contains my task:
params
{\"utf8\"=>\"✓\", \"authenticity_token\"=>\"...\", \"list\"=>{\"title\"=>\"list\", \"public\"=>\"0\", \"tasks\"=>{\"text\"=>\"task\"}}, \"commit\"=>\"Save List\", \"controller\"=>\"lists\", \"action\"=>\"create\"}"
But when I try to see what's contained within my list_params what I get omits the tasks:
list_params
{\"title\"=>\"list\", \"public\"=>\"0\"}"
And beyond that, if I add two text fields in my tasks form, say filled with "task1" and "task2", what I get in the params is only "task2", seemingly overwriting the previous task.
So my problems are
1) Am I doing my form correctly? How should I change it so it allows for multiple tasks?
2) Why doesn't my list_params return any data from the task?
and I guess as a bonus, is there anything else that I am missing to be able to save a list at the same time as it's tasks?
EDIT: Here's the github link for my project if anyone wants to try it: https://github.com/bpromas/task-list

Maybe this can help you.
Take a look at this gem: https://github.com/nathanvda/cocoon

I created a new rails app and tried to follow the code you provided.
rails new a
rails generate scaffold List title public:boolean
rails generate scaffold Task text list:references
rails db:migrate
Then I edited the models like yours
app/models/list.rb
class List < ApplicationRecord
has_many :tasks
accepts_nested_attributes_for :tasks
end
app/models/task.rb
class Task < ApplicationRecord
belongs_to :list
end
Now I looked your code and I did not understand how you initialized the tasks to be displayed in form.fields_for. I am going to print two possibilities that I am aware.
First possibility is creating a new instance of Task in _form.html.erb
<%= form.fields_for :tasks, Task.new do |task_form| %>
<p>
<%= task_form.label :text %><br>
<%= task_form.text_field :text %>
</p>
<% end %>
Second possibility is building new instances of Task in lists_controller.rb
def new
#list = List.new
#list.tasks.build
end
My list_params method is the same
def list_params
params.require(:list).permit(:title, :public, tasks_attributes: [:text])
end
For me with all the steps above the app is working properly saving the tasks for the respective list. Check out if the console is displaying a red message like that "Unpermitted parameter: :tasks_attributes", if so there is some missing step you need to look at.
The time you make this work then to change the code to display more task fields is easy, just pass an array of new Task in _form.html.erb or create more builds in lists_controller.rb
First alternative
<%= form.fields_for :tasks, [Task.new, Task.new] do |task_form| %>
<p>
<%= task_form.label :text %><br>
<%= task_form.text_field :text %>
</p>
<% end %>
Second alternative
def new
#list = List.new
2.times { #list.tasks.build }
end
Good luck !!

Related

Creating an association between a source document and each item with nokogiri

I'm building my first web application and am sort of lost. I've read the nokogiri docs, and know that I need to use Nokogiri::XML(open("http://foo")), but I'm not sure where the proper place is to use it.
I have a series model with three columns, :name, :description and :url. I want to have it so when I create a new series, the URL placed in the :url form field is parsed, and creates an episode for each item in the feed, populating its columns with xpath arguments.
Would this logic go in the series model or controller? Or in the episodes model or controller?
Here's what I have now.
series model
class Series < ActiveRecord::Base
validates :name, presence: true
validates :description, presence: true
validates :url, presence: true
has_many :episodes, dependent: :destroy
end
series controller
class SeriesController < ApplicationController
def index
#series_all = Series.all
end
def show
#series = Series.find(params[:id])
end
def new
#series = Series.new
end
def edit
#series = Series.find(params[:id])
end
def create
#series = Series.new(series_params)
if #series.save
redirect_to #series
else
render 'new'
end
end
def destroy
#series = Series.find(params[:id])
#series.destroy
redirect_to series_path
end
private
def series_params
params.require(:series).permit(:name, :description, :url)
end
end
routes.rb
Rails.application.routes.draw do
resources :series do
resources :episodes
end
root 'home#index'
end
series form partial
<%= form_for #series do |f| %>
<div class="series-form">
<div class="series-form__name">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="series-form__description">
<%= f.label :description %>
<%= f.text_area :description %>
</div>
<div class="series-form__url">
<%= f.label :url %>
<%= f.text_field :url %>
</div>
<div class="series-form__submit">
<%= f.submit %>
</div>
</div>
<% end %>
episodes has an empty controller with no model yet.
Edit: To clarify, I'm trying to import podcasts. So using The Stack Exchange Podcast's RSS feed as an example, I would place this in the series form:
Name: The Stack Exchange Podcast (from channel/title)
Description: Hosted by Joel Spolsky with Jay Hanlon and David Fullerton . . . (from channel/itunes:summary)
URL: https://blog.stackoverflow.com/feed/podcast/
and it would create an episode for each item in the feed. It would be even better if I could grab channel/title and channel/itunes:summary automatically. I hope this is possible and makes sense.
You don't really need Nokogiri for this. Just use the RSS module that comes with Ruby's standard lib.
As to your question, it looks like you want to populate the ActiveRecord model with the information from RSS feed. The simplest way, especially if you are not planning to do this for any other data in your application, is to have controller make the call to RSS and map the data to the Series and Episode model there. It would be cleaner (and easier to test) if you
create special objects that can take the RSS data and generate Series from the channel data and Episodes from the item data.
The next step would be to treat RSS as another form of data storage (much like SQLite or MySQL databases), and create a layer of code that can interact with an RSS end-point and produce a mapped model (say RSSSeries and RSSEpisodes in your case) through an interface similar to ActiveRecord (but read-only, of course). Controller then can be used to fetch the RSSSeries and access related RSSEpisodes translate it to the ActiveRecord Series and Episodes models and save them.

Rails: Create Model and join table at the same time, has_many through

I have three Models:
class Question < ActiveRecord::Base
has_many :factor_questions
has_many :bigfivefactors, through: :factor_questions
accepts_nested_attributes_for :factor_questions
accepts_nested_attributes_for :bigfivefactors
end
class Bigfivefactor < ActiveRecord::Base
has_many :factor_questions
has_many :questions, through: :factor_questions
end
and my join-table, which holds not only the bigfivefactor_id and question_id but another integer-colum value.
class FactorQuestion < ActiveRecord::Base
belongs_to :bigfivefactor
belongs_to :question
end
Creating an new Question works fine, using in my _form.html.erb
<%= form_for(#question) do |f| %>
<div class="field">
<%= f.label :questiontext %><br>
<%= f.text_field :questiontext %>
</div>
<%= f.collection_check_boxes :bigfivefactor_ids, Bigfivefactor.all, :id, :name do |cb| %>
<p><%= cb.check_box + cb.text %></p>
<% end %>
This let's me check or uncheck as many bigfivefactors as i want.
But, as i mentioned before, the join model also holds a value.
Question:
How can I add a text-field next to each check-box to add/edit the 'value' on the fly?
For better understanding, i added an image
In the console, i was able to basically do this:
q= Question.create(questiontext: "A new Question")
b5 = Bigfivefactor.create(name: "Neuroticism")
q.bigfivefactors << FactorQuestion.create(question: q, bigfivefactor: b5, value: 10)
I also found out to edit my questions_controller:
def new
#question = Question.new
#question.factor_questions.build
end
But i have no idea how to put that into my view.
Thank you so much for your help!
Big Five Factors model considerations
It looks like your Bigfivefactors are not supposed to be modified with each update to question. I'm actually assuming these will be CMS controlled fields (such that an admin defines them). If that is the case, remove the accepts_nested_attributes for the bigfivefactors in the questions model. This is going to allow param injection that will change the behavior sitewide. You want to be able to link to the existing bigfivefactors, so #question.factor_questions.first.bigfivefactor.name is the label and #question.factor_questions.first.value is the value. Notice, these exist on different 'planes' of the object model, so there wont be much magic we can do here.
Parameters
In order to pass the nested attributes that you are looking for the paramater needs to look like this:
params = {
question: {
questiontext: "What is the average air speed velocity of a sparrow?",
factor_questions_attributes: [
{ bigfivefactor_id: 1, value: 10 },
{ bigfivefactor_id: 2, value: 5 } ]
}
}
Once we have paramaters that look like that, running Question.create(params[:question]) will create the Question and the associated #question.factor_questions. In order to create paramaters like that, we need html form checkbox element with a name "question[factor_questions_attributes][0][bigfivefactor_id]" and a value of "1", then a text box with a name of "question[factor_question_attributes][0][value]"
Api: nested_attributes_for has_many
View
Here's a stab at the view you need using fields_for to build the nested attributes through the fields for helper.
<%= f.fields_for :factor_questions do |factors| %>
<%= factors.collection_check_boxes( :bigfivefactor_id, Bigfivefactor.all, :id, :name) do |cb| %>
<p><%= cb.check_box + cb.text %><%= factors.text_field :value %></p>
<% end %>
<% end %>
API: fields_for
I'm not sure exactly how it all comes together in the view. You may not be able to use the built in helpers. You may need to create your own collection helper. #question.factor_questions. Like:
<%= f.fields_for :factor_questions do |factors| %>
<%= factors.check_box :_destroy, {checked => factors.object.persisted?}, '0','1' %> # display all existing checked boxes in form
<%= factors.label :_destroy, factors.object.bigfivefactor.name %>
<%= factors.text_box :value %>
<%= (Bigfivefactor.all - #question.bigfivefactors).each do |bff| %>
<%= factors.check_box bff.id + bff.name %><%= factors.text_field :value %></p> # add check boxes that aren't currently checked
<% end %>
<% end %>
I honestly know that this isn't functional as is. I hope the insight about the paramters help, but without access to an actual rails console, I doubt I can create code that accomplishes what you are looking for. Here's a helpful link: Site point does Complex nested queries

Creating a Model in Rails within the controller of a different Model

I'm trying to implement a quote saving feature in a Rails app. I have a User Model that has_many :quotes and a Quote Model that belongs_to :user. Now I have a separate Book Model that would be the source of these quotes.
Within the Book's show.html.erb file, I have a form to save quotes for the current user
<%= form_for (#new_quote) do |f| %>
<div class="field">
<%= f.hidden_field :book_id, :value => #new_comment.book_id %>
<%= f.text_field :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
And in the Book controller, I have
def show
#new_quote = current_user.quotes.create(book_id: params[:id])
end
The quote saves fine but the problem is, since I have this Quote creation statement in the show method, everytime I go to the show.html.erb page of my Book model, it creates a Quote with an empty body.
What can I do to solve this? I was thinking it probably would involve moving this Quote creation to the actual create method of the Quote controller but I don't know how to exactly pass the parameters through.
You could just build that quote, but not save it to the database. Then the user need to send the form to save that record. Just change your show method to:
def show
#new_quote = current_user.quotes.build(book_id: params[:id])
end

Rails Create model with parent relation

I have two models: Schedules and Result.
Schedule has_one Result
Result belongs_to Schedule
Schedules data will be created first and as matches happen, we will create the results for each schedule.
As in the picture above, the Create link will take you to a page where you will create the result for the schedule. I will send the schedule_id of the schedule for which Create button is clicked.
<%= link_to "Create",new_result_url(:schedule_id => schedule.id),{:class => 'btn btn-link btn'}%>
And in the Results#New
def new
#schedule = Schedule.find(params[:schedule_id])
#result = #schedule.build_result
end
And in the View results/new.html.erb
This is where I am stuck or dont know how to submit the result form
for the schedule_id I selected
<div class="row">
<div class="col-md-4">
<%= form_for(#result) do |f| %>
<h3>Enter the result</h3>
<%= f.text_area :result,class:'form-control' %><br />
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
You might want to nest result under schedules. In your routes:
resources :schedules do
resource :result
end
And then your controller could look something like this:
class ResultsController < ApplicationController
def create
schedule.create_result(result_params)
end
private
def schedule
Schedule.find(params[:schedule_id])
end
def result_params
params.require(:result).permit(:result)
end
end
This architects your application to reflect your actual information architecture, and you don't have to worry about passing ids through hidden fields.
In your form, add:
<%= f.hidden_field :schedule_id, value: #schedule.id %>
This will pass the id of the parent schedule in with your params. Also, make sure that you permit the parameter schedule_id in your controller.
Also, to make it easier to pass the schedule_id to the results#new page, I'd change the routes file to this:
resources :schedules do
resources :results
end
That way, the route to the results#new page is now new_schedule_result_path(#schedule), which you can use in your link_to.
Edit:
Also, change your form_for to:
<%= form_for[#schedule, #result] do |f| %>
You would need to have #schedule defined in your controller.

Rails 3 - Create View to Insert Multiple Records

I have what seems like a simple query. I need to create a view that will accept multiple records based on a single model. In my case the model is Project, which has 1 foreign key (person) and 2 fields time, role. I need to create a view (form) to insert 5 roles.
<%= form_for(#project) do |f| %>
<% 5.times do |index|%>
<div class="field">
<%= f.label :position %><br />
<%= f.text_field "fields[#{index}][stime]" %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I get an error message: undefined method `fields[0][stime]'
I do not think the railscasts for nested models is what I need.
How would I go about creating this?
EDIT: The Project model code is below:
class Project < ActiveRecord::Base
belongs_to :person
attr_accessible :role, :stime
end
The Projects_Controller code for the new method is below:
def new
#project = Project.new
end
I see you're planning to make some 1-to-many relationship (Product has_many :roles).
Here's some advices.
First, take a look at the accepts_nested_attributes_for method. You need to add it to your model to be able to perform mass-create.
Second, fields_for is what you need to design nested forms.
I'll give you some example of mass-creating for a simple Product has_many :line_items case:
<%= form_for #product do |f| %>
<%= f.fields_for :line_items, [LineItem.new]*5 do |li_fields| %>
<%= li_fields.text_field :quantity %>
<%= li_fields.text_field :price %>
<br>
<% end %>
<%= f.submit "Create line items" %>
<% end %>
All you need is to write in you controller something like:
#product.update_attributes params[:product]
and 5 line_items will be created at once.
Don't forget to white-list association_attributes (see params in your logs to see it). But I think if you get the mass-assignment error you'll do it anyway :)
I hope it helps.

Resources