form_for building in rails, what can someone infer? - ruby-on-rails

I had a general question of what is going on when code like this runs:
<%= form_for(current_user.favorite_relationships.find_by_lesson_id(#lesson),
html: {method: :delete},
remote: true) do |f| %>
<div><%= f.hidden_field :lesson_id %></div>
<%= f.submit "Unfavorite", class: "btn btn-large" %>
<% end %>
specifically the very first line of code. i usually see some form of instance variable instead of
current_user.favorite_relationships.find_by_lesson_id
I can assume that this will go into the FavoriteRelationship controller's destroy action. Is there anything else someone can infer from that form above? Like what will be available or gets passed in the destroy action?

Presumably, the controller has supplied a Lesson object to the view through the variable #lesson. Your current user, a User object, presumably has_many :favorite_relationships, which in turn belongs_to :lesson, meaning there is a field within the favorite_relationships table called lesson_id.
Rails builds "magic" finder methods for your models for the fields it contains. If a model has a lesson_id field, Rails provides a find_by_lesson_id helper. Rails is smart enough to extract #lesson.id when you pass it an actual Lesson object instead of an integer.
The net result is that an object of type FavoriteRelationship is being passed into the form_for helper. This is no different than finding the object in the controller and passing it to the view via a (for example) #favorite_relationship variable.
what will be available or gets passed in the destroy action?
The only thing available to the controller on the subsequent request to the FavoriteRelationship's destroy route is the id of the object to destroy. You'll be able to access it via params[:id].

The destroy action is via AJAX (presence of remote: true)
In general, the main logic/code is refactored into either a controller or a helper method.
The #favorites = current_user.favorite_relationships.find_by_lesson_id(#lesson), IMO, should be placed inside the controller rather than the view and the view should have #favourites in the form_for part. That is the reason for the observation you've made about instance variables

Related

Why is it that the Rails convention is to write #example = Example.new twice, once in the new and then in the create method?

I am trying to understand the reasoning behind writing the line of code
#example = Example.new two times.
According to the answer below, it seems that the model object is instantiated twice but I don't understand why we need to instantiate the object for the new method.
Rails : How does "new" action called "create" action?
def new
#example = Example.new
end
def create
#example = Example.new(example_params)
if #article.save
redirect_to #example
else
render 'new'
end
end
You can't use the blank object because your view won't be able to build a form for example in that case. Usually people are building forms when they do that and to use rails form builders, the correct model object is required (in this case #example), otherwise form builder will never know about the attributes of that model object it has to build.
<%= form_for #example do |f| %>
Name: <%= f.text_field :name %>
<% end %>
In above, without #example, the form_for won't be able to figure out that it has to go to POST create action of Example. Nor will it be able to identify name field.
In case you want to remove #example = Example.new from new action, you will have to build the form on your own. But, follow the conventions, it's not recommended.
<%= form_tag('/examples') do %>
Name: <= text_field_tag :name %>
<%= submit_tag 'Submit' %>
<% end %>
In new method when we use #example = Example.new we initialise blank object of current model and #example is used to bind the form in views.
<%= form_for #example do |f|%>
if you see the html for above line it will be like
<form accept-charset="UTF-8" action="/examples" method="post">
#example in new basically bind the model attributes with form
when we user #example = Example.new(example_params) with again initialise the object with the form values i.e. values which user enter in the form and submit.
You can visit the following link to understand more http://guides.rubyonrails.org/routing.html
#example is not instantiated twice here. These are two different actions. Every time one of them is called, a new instance of the controller class is created. So #example only belongs to that instance.
You instantiate #example for #new so that it can be available in the view. Rails automatically share the instance variables from the controller actions with their corresponding views by copying them over. You do the instantiation in controllers because it is part of the controller's job. This way you have a nice separation of concerns: views don't need to bother with creating variables, only with presenting them.
You can of course do it this way:
##controller
def new
end
##new.html.erb
<%example = Example.new%>
<%=form_for example do |f| %>
##code omitted for brevity
And it will work. But this is not a good practice as you're mixing different logics in views.

Is it ok to call ActiveRecord Methods in the view Rails

Using Rails 4
I am wondering (and having a hard time finding an answer) if it is OK to call an ActiveRecord method directly from the view, such as:
<%= Article.where(approved: true).count %>
or
<%= Article.where("short_answer is NOT NULL and short_answer != ''").count %>
I realize the normal practice would be to store these in an instance variable inside of the controller, but since I am using a partial, I cannot do that.
Is doing this ok? Can it hurt? Is there a better way to go about this (such as a helper method)? Thank you!
Is doing this ok? Can it hurt?
It's definitely okay, but the problem is that you'll be calling another db query - which is the most "expensive" part of a Rails app.
#instance_variables are set once, and can be used throughout the view:
#app/views/articles/show.html.erb
#Referencing #article references stored data, not a new DB query
<%= #article.title %>
<%= #article.description %>
<%= #article.created_at %>
Because the above all uses the stored #article data, the database is only hit once (when #article is created in the controller).
If you call AR methods in the view, you're basically invoking a new db call every time:
#app/views/articles/show.html.erb
#Bad practice
<%= Article.select(:name).find(params[:id]) %>
<%= Article.select(:description).find(params[:id]) %>
<%= Article.select(:created_at).find(params[:id]) %>
To answer your question directly, you would be okay to call that data IF you were only counting database-specific data.
IE if you were trying to count the number of #articles, you'd be able to call #articles.size (ActiveRecord: size vs count)
The prudent developer will determine which data they have in their controller, and which they need to pull from the db... doing all their db work in the controller itself:
#app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def index
#articles = Article.where(approved: true) #-> could use a scope here if you wanted
end
end
#app/views/articles/index.html.erb
<%= #articles.size %>
Nithin's answer is great but won't get past the consideration that you have to determine whether you need to call the db explicitly, or use already-invoked data.
Finally, in regards to using a partial, if you have to pass that data every time, you may wish to use some sort of conditional data to determine whether you need to call the db or not:
#app/views/shared/_partial.html.erb
<% approved ||= Article.approved_articles.size %>
<% short ||= Article.short_answer_presence.size %>
This will allow you to set locals IF you want, and also have "defaults" set if they aren't set.
You should mostly do
class Article < ActiveRecord::Base
....
scope :approved_articles, where(approved: true)
scope :short_answer_presence, where("short_answer is NOT NULL and short_answer != ''")
end
In your controller method
#approved_articles_count = Article.approved_articles.count
#short_answer_presence_count = Article.short_answer_presence.count
and use those variables in view.
In case of partials, as said my Raman you can do that.
<%= render partial: "form", locals: {approved_articles_count: #approved_articles_count, short_answer_presence_count: #short_answer_presence_count} %>
You can always pass these variables inside a partial using locals:
<%= render partial: "form", locals: {zone: #zone} %>
Its always a good practice to define the instance variables in controller, it does not hurt but you don't end up doing business logic inside a view.

Why does form_for(#object) do |f| work in the _form.html.erb but not in the index.html.erb

Ok, So i'm trying to place the form found in the "_form.html.erb" in the "index.html.erb" of my ruby project crashes with the error
"First argument in form cannot contain nil or be empty"
<%= form_for(#customer) do |f| %>
I know that changing the #customer to Customer.new could fix this but I would like to know why this isn't necessary in one file and it is in another
Why is this happening and how do I make a form that will update the sqlite db on the index page.
#customer is a variable that must be created somewhere in the corresponding controller action. If your #index controller action defines a variable by that name, then you'll be able to use it in the view template; otherwise you'll need to create it like this:
#customer = Customer.new # (or whatever the value is)
When Rails processes a request, it just executes a big (and complex) lump of code that's created from a bunch of different files. First it executes the appropriate controller action, then it executes any Ruby code found inside the corresponding view template. So any variable (or any method name) that is used in the view template, was first defined at some point before that: either in the controller action, or in one of Rails' countless built-in helper files.
When I am using form_for in a index or show page I like to do is set it to new
<%= form_for Customer.new, url: {controller: "customers", action: "create"} do |f| %>
...
<%= f.submit %>
<% end %>
that way there is a object to be created, I also I like to pass in the controller and the action.

Why is my Rails form helper written improperly?

I know I've written it wrong, but I'm looking at the documentation and can't figure out how.
My model is Quote and has three fields, body, attribution, and work, all strings. The form is intended to add a new quote to a page of quotations.
on main/index.html.erb
<%= form_for(:quote, url: {action: 'create'}) do |f| %>
<%= f.text_field :body %>
<%= f.text_field :attribution %>
<%= f.text_field :work %>
<%= submit_tag "Submit" %>
<% end %>
in main_controller.rb
def create
Quote.create(body: params[:body], attribution: params[:attribution], work: params[:work])
end
The form submits, and an entry is saved to the database -- but it's a totally blank entry. I'm not sure why. Help would be appreciated!
Three things:
The way rails forms are supposed to work, you're not meant to get body, attribution, etc independently, they should be wrapped up into a quote object. But...
In your form, your not properly binding an object to the form the way rails expects. You can read more in the documentation here: http://guides.rubyonrails.org/form_helpers.html#binding-a-form-to-an-object. You could also generate a fake scaffold rails generate scaffold HighScore game:string score:integer to generate a fake model and see an example of how it's supposed to work. The default scaffolding even has simple examples of how to deal with save errors.
Finally, as #Paven suggested, when you get confused, be sure to look at what's going on in your log - i.e. what params are being posted to your create action. That is always helpful and a good way to diagnose problems quickly.
Your form does't need the action argument. The form_for helper uses ActiveRecord objects to determine the path, meaning as long as you build your object correctly, you won't need to determine your path individually:
<%= form_for #quote do |f| %>
Secondly, you'll want to look at your create method:
#app/controllers/quotes_controller.rb
def new
#quote = Quote.new
end
def create
#quote = Quote.new(quote_params)
end
private
def quote_params
params.require(:quote).permit(:body, :attribution, :work)
end
The problem is you're not sending an ActiveRecord object to your form_for helper. You can read the explanation here:
In Rails, this is usually achieved by creating the form using form_for
and a number of related helper methods. form_for generates an
appropriate form tag and yields a form builder object that knows the
model the form is about. Input fields are created by calling methods
defined on the form builder, which means they are able to generate the
appropriate names and default values corresponding to the model
attributes, as well as convenient IDs, etc. Conventions in the
generated field names allow controllers to receive form data nicely
structured in params with no effort on your side.
In order to get the form working correctly, you need to be able to provide a valid ActiveRecord object (#variable), which the helper can use to determine the url etc
My code above helps you provide a new ActiveRecord variable, and allows you to use it in the form. This should allow the form_for method to send your data to the create method, which will then create & save an object in the db for you

how form_for works in Ruby on Rails

I am an newbie. I have read the API documentation. But still don't understand how form_for works.
Firstly, from Ruby on Rails Tutorial, the form for follow button:
<%= form_for(current_user.relationships.build(followed_id: #user.id)) do |f| %>
<div><%= f.hidden_field :followed_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
I understand current_user.relationships.build(followed_id: #user.id) means a new record. But why can we not just submit and trigger controller to save the record without hidden_field? Why do we still need to post followed_id to controller?
Secondly, in hidden_field, what does :followed_id means? I believe that is a symbol, i.e. it equals only "followed_id" not a variable of id. If that is only the name of the input field, then what is its value?
Thirdly, how does form_for know where the submission should be sent to? Which controller and action the form_for will post to?
Fourth, how does params work with form_for? In this follow button case, params[:relationship][:followed_id] will return #user.id in controller. How does it know the first hash attribute is :relationship? We have neither mentioned form_for :relationship nor form_for #relationship.
I know these questions can be very dumb, but I am really stuck. Any help will be appreciated.
I didnt do that tutorial so mind me if i dont answer directly to your question.
Take a look at the rails guide about form helpers and it explains in details your questions, probably in a more articulate way than i can.
form_for(path/to/your/controller/action) is a helper method to create HTML form elements with the url path to the POST or GET request. The helper knows if it should be a new record or an update record based on what you are asking to do in your controller action.
For example
In your controller
def new
#my_instance_variable = Myobject.new
end
In your view new.html.erb
<%= form_for #my_instance_variable do |f| %>
...
<% end %>
In your case the logic was directly written in the helper and you could also directly write
<%= form_for Myobject.new %>
Both will result with the following html
<form action="/myobjects/new" method="post">
# in this case rails knows its a `POST` request because the route new action
# is by default a POST request. You can check these routes and their request
# by using `rake routes` in terminal.
Then the hidden_field is another helper to contain a value, in your case the #user.id that will be passed as parameter then saved as a Create or update action for the given object. The reason it doesnt add the value in the hidden field tag is because you already have a model association that knows the id of user since the link of form uses the build method with user id.
Last part you need to understand the form_for link logic
current_user.relationships
# implies the association of the current_user has many relationships
current_user.relationships.build
# .build is a method to populate a new object that can be save as a new record
# means you will create a new relationship record by populating the user_id
# column with the current_user.id and the followed_id with the target #user.id
After reading the book The Rails 4 Way, I understand form_for better now.
11.9.1.5 Displaying Existing Values.
If you were editing an existing instance of Person, that object’s attribute values would have been filled into
the form.
in this way, when we build the relationship by usingcurrent_user.relationships.build(followed_id: #user.id), the relationship instance will be created and gain attribute followed_id. So that, instead of "creating" a relationship, we are actually editing the relationship by the form.
Then Rails will know you are editing and load the existing attribute "followed_id" to the field. Therefore, we don't need to assign value to the field like using f.hidden_field :followed_id, value: #user.id.
And the reason why we have to use a field to pass followed_id to params is because HTTP server is stateless, it doesn't remember you are creating a relationship with which user.
One of the advantages of writing form_for current_user.relationships.build(followed_id: #user.id) instead of standard form_for #relationship is we don't need to write "if-condition" in controller like this:
unless current_user.nil?
if current_user.following?(#user)
#relationship=current_user.relationships.find_by(followed_id: #user.id)
else
#relationship=current_user.relationships.new
end
end
params will be sent to the controller which belongs to the instance's model. "post" method will go to action create, "delete" will go to destroy, "patch" will go to update, etc.
params will be a hash with another hash inside like { instace_name: { field_1: value1, field_2:value2 } } or full params as below
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"afl+6u3J/2meoHtve69q+tD9gPc3/QUsHCqPh85Z4WU=",
"person"=>{"first_name"=>"William", "last_name"=>"Smith"},
"commit"=>"Create"}

Resources