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.
Related
I've got a relatively complex form I'm trying to code efficiently. Most online examples of nested forms deal with very clear hierarchical relationships, mine does not.
Below is the data model. The essential job of the form is to create a "Job Entry" record while at the same time creating a new "Entity" record - which is a person. Several relationships come to bear in this form.
A "Job" is already created. The Job has 1-to-many "Questions" which exist before the user hits this form. However, they must fill in "Answers" to the questions. They also choose one of many pre-created "Job Roles".
The question is how to leverage "form_with" and "fields_for" for all these inter-related models.
My assumption is to ditch built-in helpers and just use a form_tag and roll everything together manually. But maybe there is a "correct" way to roll forms that do not necessarily abide by parent-child relationships? In my example, there is no pure top-level object to start with since many child objects already have records, but maybe I am wrong and Entity should be the starting point?
Entity has_many Job_Roles
Entity has_many Job_Entries
Job has many Job_Roles
Job has_many Job_Entries
Job has_many Questions
Question has many Answers
Answers belong_to Entity
Agency has_many Job_Entries
etc...
There is no need to ditch the built-in helper: Rails has thought about that, it's called nested forms.
Here is an example:
<%= form_with model: #job do |f| %>
Job entries:
<ul>
<%= f.fields_for :job_entries do |je_form| %>
<li>
<%= je_form.label :kind %>
<%= je_form.text_field :kind %>
<%= je_form.label :street %>
<%= je_form.text_field :street %>
...
</li>
<% end %>
</ul>
<% end %>
You can nest as many children forms as you'd like using fields_for. Don't forget to use accepts_nested_attributes_for in the parent models.
Nested forms as Mike proposed are a rails-way solution of your problem. It is ok - but for complex forms, with lot of validations, it may not be the best solution). You could consider using a FormObject pattern instead.
FormObject is a simple ruby class that uou can keep it i.e. in Forms folder and use as below:
class JobEntryForm
include ActiveModel::Model
attr_accessor :customer_id, :agency_id, :name, :question_text #you can use atributes from different models
validates :customer_id, presence: true #you can validate yu attributes as you want - your in necessity to use model validation
def initialize(attributes:)
#customer_id = attributes[:customer_id]
#agency_id = attributes[:agency_id]
#name = attributes[:name]
#question_text = attributes[:question_text]
end
#implement whatever you need
end
than in you controller:
#form = JobEntryForm.new
and you your view:
<%= form_for #form do |f| %>
<%= f.label :customer_id, 'Customer' %>:
<%= f.text_field :customer_id %>
...
<%= f.submit %>
<% end %>
And - at the end - in your controller create method:
def create
#form = CreateJobEntry.new.call(attributes: form_params) #service object to keep your controller clean.
end
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 !!
I'm trying to get file uploading to work with a nested fields_for tag in a Rails 4 app. I've followed several Railscasts, namely: 253, 381, 383, but still can't quite get it fully functioning. Also using Carrierwave & jquery file upload.
Basic app structure is as follows:
blogpost.rb
class Blogpost < ActiveRecord::Base
has_many :blogpics
end
blogpic.rb
class Blogpic < ActiveRecord::Base
belongs_to :blogpost
end
blogposts_controller.rb
def new
#blogpost = Blogpost.new
blogpic = #blogpost.blogpics.build
end
blogpost_form.html.erb
<div>
<%= form_for #blogpost do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.hidden_field :post_id %>
<%= f.text_field :title %>
<%= f.text_field :location %>
<%= f.text_area :content %>
<%= f.fields_for :blogpics do |builder| %>
<%= builder.file_field :image %>
<%= builder.hidden_field :blogpost_id %>
<% end %>
<p><%= f.submit %></p>
<% end %>
Uploading a single file works. But, adding ":multiple => true, :name => 'blogpic[image]'" to the file field breaks functionality and no files upload.
When I edit blogposts_controller.rb as such:
def new
#blogpost = Blogpost.new
3.times do
blogpic = #blogpost.blogpics.build
end
end
I am able to input three files individually, then upload successfully. Is there any way I can achieve this functionality while being able to drag & drop multiple files into one input?
I really appreciate any help and direction, thanks.
Your blogpost model is missing an accepts_nested_attributes association.
class Blogpost < ActiveRecord::Base
has_many :blogpics
accepts_nested_attributes_for :blogpics
end
I'm not quite sure how handling multiple files in one dialog box. I'd imagine you'd be using some javascript to detect that multiple files were selected, and creating field forms for each of them.
You can pass :multiple => true as a param on builder.file_field :image. See http://apidock.com/rails/ActionView/Helpers/FormTagHelper/file_field_tag for details
With the multiple attribute on the file input, you can drag and drop ONTO the input element
See for details http://www.html5rocks.com/en/tutorials/file/dndfiles/#toc-selecting-files
I've run into the same issue (where multiple: true breaks the nested form) and my understanding is that you have to manually transform the params before the controller receives it. If you inspect (using pry or debugger) the params hash, you need to compare between submitting Parent Model with several input files (on individual inputs) VERSUS Parent Model with multiple input files (in one input). The former creates an array of child objects (each with their own file), while the latter creates only one child object that has all the images in one array.
I have a model called teacher that I'd like to add ratings to (5 star). Currently, I implement this by adding a ratings nested route (resource rating) inside of my teacher resource. Then I created a model: rating with (id, user_id, teacher_id, ratings, ...). Then I created a form with hidden fields, one of which is called stars. When a user clicks on a star, I use jQuery to send an AJAX request to create/update the rating for that user and teacher.
My confusion is this: I'm having two separate forms on the page. I have a form for writing the reviewers comments. This form has two fields: title, comments (and submit). Then I have the ratings form with hidden fields. Is this the right way to go about something like this? It seems to me that I should really have the ratings model fields somehow embedded in the main review form.
Any help highly appreciated. Thank you.
[EDIT]
I've updated my application so that instead of rating a teacher object, users now rate a comment on a teacher
my setup is something like this:
routes
resources :comments as :teacher_comments do
resource :rating
end
models
comment
has_one :rating
attr_accessible :body, :rating_attributes
accepts_nested_attributes_for :rating
rating
belongs_to :comment
attr_accessible :stars, :user_id, :teacher_id, :comment_id
view
<%= form_for( #comment, :remote => true, :url => teacher_comments_path ) do |tc| %>
<%= tc.text_area :body, :maxlength => 450 %>
<%= tc.fields_for :rating do |builder| %>
<%= builder.text_field :stars %>
<% end %>
<% end %>
I don't see the text_field for the stars. It's just not showing up. Is there something I missed?
Indeed, it's generally better to have all these fields in a single form (good for user experience).
Edit:
You might use the method accepts_nested_attributes_for (as you suggested in the comments below). Put the following in your parent Model (teacher); then you should be able to create a single form to handle inputs for both Models:
in the model:
class Comment < ActiveRecord::Base
has_one :rating
accepts_nested_attributes_for :rating
end
in the controller:
def new
#comment = Comment.new
#comment.rating = Rating.new
end
Ryan Bates gives a detailed screencast on the use of these concepts here: Nested Model Form. I recommend it for users who want to know more ins and outs.
Original:
This means that you'll need to point the form to an action that can handle both types of input. You can still use form_for if you like, but specify an action other than your default (or change the code within the default action in your teacher_controller.rb file):
<%= form_for #teacher, :url => {:action => 'create_and_rate'} do |f| %>
Since rating is a Model distinct from teacher (whose form we just created), you'll want to use the generic _tag form helpers for the rating fields.
<%= text_field_tag :rating, :name %> # rating's fields should use the generic form helper
<%= f.text_field :name %> # teacher's fields can use the specific form helper
Since you are pointing to a non-RESTful action, add it to your routes file.
resources :teacher do
:collection do
post 'create_and_rate' # this will match /teachers/create_and_rate to TeachersController#create_and_rate
end
end
I am learning rails and am building a mini poll app as a learning tool. I know how I can create forms by hand but when I want to build radio buttons for a form I am confused as to how to do this: The code that renders my polls choices are as follows:
def show
#poll = Poll.find(params[:id])
end
# show.html.erb - this is where I want to render the radio buttons/form
<h2><%= #poll.question %></h2>
<ul id="choices">
<% #poll.choices.each do |choice| %>
<li>
<%= choice.choice %> →
</li>
<% end %>
</ul>
My models are pretty straight forward:
class Poll < ActiveRecord::Base
has_many :choices, :dependent => :delete_all
validates :question, :presence => true
end
class Choice < ActiveRecord::Base
belongs_to :poll
validates :choice, :presence => true
end
My question is how do I build a form with radio buttons based on the above? Basically I want my votes column to increment by 1 when this form is submitted, I can handle that but my confusion is how to build this form, can anyone help me?
Thanks
Jeff
With out-of-the-box rails form helpers, something like this should work (not tested):
<%= form_for(#poll) do |f| %>
<% #poll.choices.each do |choice| %>
<%= radio_button_tag :chosen_id, choice.id %> <%= choice.choice %>
<% end %>
<% end %>
Then in the update method of your PollsController, do your model update based on the value of params[:chosen_id].
This is not the most elegant way to do this, but lacking more information about your models, it should be enough to get you started. This is pretty basic Rails stuff, by the way. I suggest you read up on Rails form helpers for the basics, then maybe take a look at nested forms (which my example is not), and maybe use some of the great gems that simplify form handling such as simple_form, as Michael Durrant suggested in the comment.