Many-to-many associations and REST? - ruby-on-rails

Newbie question, you've been warned!
I'm trying to implement a sample Rails app with a many-to-many association, people owning movies, and I'm trying to figure out how exactly to implement the UI for it. It's my understanding that REST requires everything to be a resource, so in this case "User" (person), "Movie" and "Possession" (the joint table) (oh, the puns).
Now the interesting part, the UX. Let's say I have a user dashboard where all of your movies are listed.
Let's say the user wants to add a movie that he owns. How do you do this in REST? It's trivial with a custom action that one could add to the User controller, but the point is not to go beyond the basic 7 REST actions, right? Therefore I'd have to first do a "new" on a movie and then do a "new" on a possession, which are two operations. How do I collapse them into one?
Basically I feel I'm not quite understanding how to maintain REST as soon as multiple models are involved and would appreciate a tip.
Thanks!

Happily, Rails has some magic just for this common scenario. Assuming a model like this:
class Movie
has_many :users, :through => :possessions
end
Your view:
<%= form_for [current_user, Movie.new] do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<% end %>
Basically this form will POST to MoviesController#create and will pass along current_user.id as a user_id parameter that (the default) MoviesController#create will know to associate with the Movie it creates. Take a look at the documentation for FormBuilder#form_for for more information.
You could also do this the other way around, by the way:
class User
has_many :movies, :through => :possessions
accepts_nested_attributes_for :movies # magic!
end
And the view:
<%= form_for current_user |user_form| %>
<%= user_form.fields_for current_user.movies.build |movie_fields| %>
<%= movie_fields.label :title %>
<%= movie_fields.text_field :title %>
<% end %>
<% end %>
In this case the form will submit to UsersController#update and its parameters will look like this:
{ :id => 123,
:movie => {
:title => "The Red Balloon"
}
}
...and the controller will know to create the Movie object. For more information check the documentation for FormHelper#fields_for.

Related

Complex Rails Form Best Practices

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

How to setup this relationship and display the correct data

Not sure how to set this relationship up correctly.
3 tables: Users, Posts, Statuses
Models -
User Model
has_many :posts
Post Model
belongs_to :user
has_one :status
Status Model
#Not sure about this.
Tables –
Users: name, email, password_digest
Posts: user_id, status_id, post_title, post_content
Statuses: user_id, status_title, status_active
The user can create their own statuses, so I am not sure if the status should belong to the User or the Post.
In the Users controller, for the new action I have the following to help generate the status options in a dropdown list when making a new Post:
def new
#post = Post.new
#options_statuses = Status.where(:admin_user_id => session[:id])
end
The code to create the new Post(from within a partial):
<%= f.label(:status_id, "Status") %><br/>
<%= f.select(:status_id, #options_statuses.map { |s| [ s.status_title, s.id ] }) %><br/><br/>
<%= f.label(:post_title, "Title") %><br/>
<%= f.text_field(:post_title) %><br/><br/>
<%= f.label(:post_content, "Content") %><br/>
<%= f.text_area(:post_content) %><br/><br/>
The Post info is saved correctly to the database. The problem is when i want to show the posts via the Index or Show actions. Currently I have the following:
<% #posts.each do |post| %>
<%= post.status_id %> - <%= post.created_at %><br/>
<%= post.post_title %> - <%= post.post_content %>
<% end %>
Instead of showing the status_id, I would actually like to display the status_title but am not sure how to do this.
Any help is greatly appreciated.
First of all, since your Post model has a status_id attribute, this means Post should belong_to :status instead of the has_one :status. Status should then either has_one :post or has_many :posts.
Now, for your question: You can achieve this by delegating title to the associated status object. (Hint: think of these objects as objects here -- forget about the database behind them!) Here's how:
# Post model
delegate :title, to: :status, prefix: true
This will define a method behind the scenes that looks like this:
# Post model
def status_title
status.title
end
For more on delegate see the api docs.

Rails - how to store "has_many" checkboxes association in database?

I have the table User and the table Categories.
The relation between these two tables:
user has_many :categories
category belongs_to :user
I would like to display on the user's (standard) edit page the list of categories with checkboxes. When this user would check some of the checkboxes, I would like to save them and then display as checked when he open the page the next time.
But how to render the checkboxes in the view? And how to store information about what checkboxes the user checked in database?
My first idea was like to create a table like user_categories with user_id and category_id, but not sure how effective this approach is.
What's the best way to implement this task nowadays?
Not much has changed recently except for the introduction of strong parameters. You still want to use either a has_and_belongs_to_many or has_many :through relationship and then just assign the ids of the relationship directly. You'll need to setup a form that passes an array of ids that are selected so that they come in like {user: {category_ids => [1,2,3]}.
class User < ActiveRecord::Base
has_and_belongs_to_many :categories
end
In the controller
def update
current_user.update(user_params)
end
def user_params
params[:user].permit(
{:category_ids => []}
)
end
you can take the habtm approach.
then in user form partial, add field
<div class="field">
<%= f.label "Categories" %><br />
<% for category in Category.all %>
<%= check_box_tag 'user[category_ids][]', category.id,
#user.category_ids.include?(category.id), :id => dom_id(category) %>
<%= label_tag dom_id(category), category.name, :class => "check_box_label" %>
<% end %>
</div>
where category.name is the field in your category model.
this should work.
then in your user index view,
<% #users.each do |user| %>
<%= user.categories.collect(&:name).join(",") %>
<% end %>
it should show the associated category of the user. dont forget to add :category_ids in your user model attr_acessible field. else mass assingment security will pop up
You probably want to implement a many to many relationship using has_many :through rather than having categories belong to a user (means a category can only ever have one user, is that what you want?
Have a read of has_many :through to get you started. Once you've done that the checkboxes are easily implemented using the collection_check_boxes helper.

Add star ratings to my model rails3

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

Nested forms in rails - accessing attribute in has_many relation

I have a user and a profile model. One user can have many profiles. I need to access only one information from the profiles section (viz the phone number) in my user model during the user creation process. Hence I'm trying to get it done through attr_accessible. My user.rb looks like this.
has_many :profiles
attr_accessible :handle, :email, :password, :profile_mobile_number
attr_accessor : :profile_mobile_number
The problem that I'm facing is that when I try to call the getter method profile_mobile_number in a method in user.rb (the method is private, though I think it doesn't matter), I'm getting a null value. I use the following in my users/new.html.erb form
My question is what is the right way to do this? Should I use <% f.fields_for :profile do |ff| -%> or <% f.fields_for :profiles do |ff| -%> (notice that the second one is plural). When I use the plural :profiles, I don't even see the fields on the form. What am I missing here? And what is the tense that needs to be used in model user.rb? :profile_phone_number or :profiles_phone_number? Thanks.
You could do something like the following:
<% form_for #user, :url => { :action => "update" } do |user_form| %>
...
<% user_form.fields_for :profiles do |profiles_fields| %>
Phone Number: <%= profiles_fields.text_field :profile_mobile_number %>
<% end %>
<% end %>
But since you already have an association, then might as well use 'accepts_nested_attributes_for'
You should watch RailsCasts Nested Model Form.
thanks Ryan Bates great work.
http://apidock.com/rails/v3.2.8/ActionView/Helpers/FormHelper/fields_for
This api dock link list many Nested Attributes Examples including one-to-one, one-to-many. It's very helpful!
You can use 'accepts_nested_attributes_for' to do this; but there's a little trick in forms:
You must use the singular, and call fields_for for each profile, like this:
<% form_for #user do |f| -%>
<% #user.profiles.each do %>
<% f.fields_for :profile_attributes, profile do |ff| -%>
<% end %>
Notice that is :profile_attributes, instead of just :profile.

Resources