So I'm working on a Rails project in which I have a frontend interacting with multiple controllers to retrieve information. I have instructors and courses controllers and I am trying to implement a search feature where I can select which controller to search from using a drop down box, and when I click the search button, it will only search the selected controller.
My search box on the home page looks like this:
http://i.stack.imgur.com/o7yzk.png (SO won't let me embed an image)
The code to make it is below:
<%= form_tag(params[:option], :method => "get", id: "search-form") do %>
<%= select(:option,options_for_select([['Instructors', 'instructors'], ['Courses', 'courses'],['Departments','departments']])) %>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil, html: {class: "button button-red"}%>
<% end %>
Special note on line 1 where I use params[:option], this populates with the information from the 'option' field, so if I select 'Instructors', the option becomes instructors.
Problem: On the homepage when I enter a name and click search the url that is created is as follows:
http://0.0.0.0:3000/?utf8=%E2%9C%93&option=%2Finstructors&search=John
and it takes me right back to the homepage. I can see in the debug information, that it isn't even getting to the right controller, it's only looking at the static pages controller.
If I click the search button again it brings me to the correct page with the correct search results:
http://0.0.0.0:3000/instructors?utf8=%E2%9C%93&option=%2Finstructors&search=john
I'm using Solr to do the searching, and I have everything working properly except this pathing issue which requires multiple clicks. My controller code for instructors index looks like this:
def index
if params[:search] != ''
#search = Instructor.search do
fulltext params[:search]
end
#instructors = #search.results
else
#instructors = Instructor.all
end
end
I feel like I am making an architectural mistake somewhere, and any input would be appreciated.
I feel like I am making an architectural mistake somewhere, and any input would be appreciated.
Exactly right. The mistake is this: params[:option] is a serverside value. In order to get to the server, you are creating a request using the form submission. The form decides where to submit using the serverside params[:option]. It's like trying to pull yourself by your own bootstraps.
In more detail, what happens is this:
The serverside receives the request for your /
The serverside renders your form. The form's action is set to params[:option], but such an option is not set. The default value for a form action when it is not specified is the same page you're on.
The clientside displays the form. The user selects Instructors and submits the form.
The clientside contacts the form's action (i.e. the same page), and transfers the form's values (option and search)
The serverside / receives the request, and renders the form
This time, params[:option] is set to instructors
The client submits the form again; this time, the form has a correctly set action (the received params[:option] from the last cycle)
Two easy approaches to solve this would be:
Use JavaScript to dynamically modify the form's action attribute as the option is selected
Make a dedicated search controller/action that will handle search for both kinds, depending on the value of params[:option]
Make another action at serverside that will redirect your request to one of the other two actions, depending on the value of params[:option]
Related
I'm trying to build a form that allows a user to add products to an order.
The way I have it setup so far is that a user will select from 2 dropdown boxes and type into 1 text field
1 - the product they want
2 - its size
3 - the quantity they want.
What I hope to do is have the user click a link_to tag to "Add" this item to their order.
I was thinking I could do this via ajax and build the associative record in my controller and have it render on the page when the request returns.
When the user is done with their order and hits submit I can create my Customer Order with the products they wish to buy.
Am I approaching this correctly?
e.g. my form has the following:
<%= collection_select :order_line_item, :cake_id, Cake.order(:name), :id, :<%= grouped_collection_select :order_line_item, :cake_size_id, Cake.all, :cake_sizes, :name, :id, :name %>
<%= label_tag :quantity %>
<%= text_field_tag :quantity %>
<%= link_to "Add to order", add_to_order_path, {method: :post, remote: true} %>
Am I approaching this correctly? I then need to be able to add the fields above to the ajax post so I can populate the associative record with the relevant values.
Am I approaching this correctly?
I don't know about 'correctly'. But, I can imagine some alternatives.
Here are some sketches:
One Option:
This approach assumes that the Order is already saved so that you can associate a Product with that order. Perhaps Order has a status.
You could wrap that whole bit (product, size, quantity) in its own form (not embedded within your order form).
Have the form submit via js using remote: true (if you're using Rails 5, then this may be the default behavior).
When the user clicks on "Add", you will receive the field values as parameters in your controller where you can associate the Product with the Order.
Then, render back an HTML blob that can be inserted into the DOM (perhaps an order row?)
Use js to insert the blob and clear the form.
Another Option:
You could leave that whole bit (product, size, quantity) as not a form and have it reside outside your form.
Wrap it all up in a div.
Convert that link into a span or something similar.
Attach an .on 'click' event (I'm assuming jquery, you don't specify, so I'm going to run with it) to the wrapper.
When the link is clicked, the click event will bubble up to the wrapper.
Have the wrapper submit the field values via ajax.
Proceed as above.
I wouldn't really recommend this approach as it seems to me that you're basically replicating the functionality of a remote form. But, there is...
Yet Another Option
This approach does not require that the Order already exists.
You could have a hidden order item row outside of your form.
You construct your page as above in Another Option.
Now, when the user clicks the "Add" button, clone the hidden order item row.
Fill in the cloned order item with the appropriate values.
Insert the cloned order item into your Order form.
When the user clicks "Order" or "Submit" or whatever they click when they're done, you'll get all of the order rows as field sets.
Process the order line items along with the form. (Some folks might suggest accepts_nested_attributes_for, but I never use that.)
I suspect there are others. Or perhaps variations.
Is it possible to fire up an custom action when user clicks 'Search' button on search form?
There is an mechanism in our app to save every URL the app has hit. In our search form, when clicking 'Search' button, there will bring up the search result page. The problem is that the URL for the search result form was not saved. The Back button brings back the search page (for setup search params) instead of the search result page (because its URL was not saved).
Here is the search form for model configs:
<h4>Search Form></h4>
<%= simple_form_for #config, :method => :get, :url => search_result_configs_path do |f| %>
<%=render :partial => 'search_params', :locals => {f: f} %>
<%= f.button :submit, t('Search') %>
<% end %>
The URL for the search result looks like this (with the search params set by user) after user clicks Search button:
http://localhost:3000/configs/search_results?utf8=%E2%9C%93&engine_config[start_date_s]=&engine_config[end_date_s]=&engine_config[engine_id_s]=1&engine_config[argument_name_s]=&engine_config[commissioned_s]=&commit=%E6%90%9C%E7%B4%A2
This is the URL we would like the app to remember. We figure we need custom action triggered when a user clicks 'Search' button. Is it possible?
Route
Firstly, calling a custom application is actually quite a simple process - you just need to call its route:
#config/routes.rb
resources :search do
collection do
get :custom_action
end
end
This will allow you to use the likes of form_tag to call the custom route:
#app/views/your_controller/view.html.erb
<%= form_tag search_custom_action_path, method: :get do %>
...
<% end %>
--
Form
Secondly, you're using simple_form for your search form.
This is completely fine, but the problem you have here is that when you use this, it has to have a ActiveRecord object to populate the form with. This is probably where you're getting confused, as to do this, you need ot make sure #config is available every time you load that form, which I imagine can be a lot.
We've created a search form here:
Although in Rails 4, we used a form_tag for this form, as it allowed us to create & display the form where-ever we need in the app. This allows us to pass the required params through the form & access them on the other side
--
Params
You mention you want to "save the URL" - what do you mean by this?
Surely you'd prefer to save the params?
If this is true, the way to do this is actually relatively simple - you'll get access to the params hash in your controller when you send the request through:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
def custom_action
params[:your_param] #-> this is accessible here
end
end
The bottom line is if you wanted to save the query strings, you'll have to create a model called Search or similar, allowing you to pass the params through when you process the custom action in your controller, just like you would any other ActiveRecord object
Model:
Users have expenses. Expense has a status.
View:
As users add their expenses, they are shown in a list. Each expense row has a form button on the end, which is used to submit the expense (changing the status of the expense). This allows users to add expenses they have not completely filled out, and submit them when they are ready. There is no parent form on this page, just the form buttons which submit the expense to a method which changes the status, and then reloads the page.
Currently it works great, but users have asked to be able to "submit all" the expenses that are showing on the view with a single button.
Question:
What is the proper way to handle this in rails? Should I find a way to gather the array of expense id's and then submit a separate form? Is there a way to ask for a set of records present in a view with a certain status?
Thanks!
Another option, if I'm thinking about this right (big if), would be to wrap your page in a User form. Then you could have something like...
<%= form_for(#user) do |f| %>
<% #user.expenses.each do |expense| %>
<% f.fields_for expense do |e| %>
<!-- expense form -->
<% end %>
<% end >
<% end %>
This is something you could submit as a whole. I'm having trouble picturing what a single expense addition might look like, but hopefully this gets you a little further down the road.
Edit: in addition to having this User form on the page, you could have an "extra" Expense form to create an expense. When you submit a new expense, that expense appears in the list under the user form, where it can be edited or submitted, either as part of a group or individually (as part of a "group" of 1).
custom controller action:
def update_all_expense_statuses
expenses = current_user.expenses
ExpenseUpdater.new(expenses).update_expense
redirect_to :back
end
expense updater class:
class ExpenseUpdater
def initialize(expenses)
#expenses = expenses
end
def update_expense
#expenses.each do |expense|
expense.update_attributes(status: 'paid')
expense.save
end
end
end
This is just an example of one way to update all the user's expenses with a custom controller action. Just call the controller method from a link_to:
<%= link_to "Update all expenses", update_all_expense_statuses_path %>
Remember to add it to your routes. Hope this helps.
The first thing you should do is change the forms to submit remotely, ie make an ajax request. Then you're not reloading the whole page. Check out Rails' various "remote" form helpers, eg "remote_form_for".
Then, write a javascript function to submit all the forms for inputs that have changed since the page loaded. You'd probably want to add a "changed" (or similar) class to the parent form in an onchange event in each input, to facilitate this. I think this is the best way to handle the "status" thing you're asking about. Make a "Submit all" button which calls this function.
Use a form/service object http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ to encapsulate expense report
I have a set of buttons displaying on my webpage. The effect of clicking one of the buttons needs to be that a call is made to an external API (and maybe the response being received, and updating something on the page).
Some additional information: these buttons are placed on the page by a partial, and make up part of a list of users. The buttons are intended to activate and deactivate the users being listed. I'm not sure if this setup will affect the best approach for doing what I want to do, so I thought it would be worth mentioning.
How should this be done? Should the buttons be links to some controller within my rails app? Wouldn't that require the page to be reloaded when the button is hit? Can I offload that request to ajax?, etc.
I don't know the best way to approach this, and any guidance would prove invaluable.
Ok. I believe I have found a good implementation of this.
The trick is to create a form encapsulating the button in order to hit the proper controller when the button is clicked. In my case, I used the rails form_tag function to generate my button within my _list_item.html.erb partial view for my Developer controller as follows:
<div id=<%= list_item.id %>>
<%= form_tag "/Developer/toggle", :method => "get", :remote => true do %>
<p>
<% if list_item.inactive? %>
<%= submit_tag "Activate", :name => nil %>
<input type="hidden" name="act" value="activate" />
<% else %>
<%= submit_tag "Deactivate", :name => nil %>
<input type="hidden" name="act" value="deactivate" />
<% end %>
</p>
<input type="hidden" name="dev_id" value=<%=list_item.id%> />
<% end %>
</div>
There are 2 things that should be called to attention within this partial.
Since this is a partial rendered as part of a list, you want to give each list item a unique id so that your javascript will act on only that element. This is done in the first line, <div id=<%= list_item.id %>>, which I know will be unique because each Developer in the list necessarily has a unique id.
:remote => true is a necessary argument to the form_for function this is what causes the request to be made in the background as opposed to loading a new page.
This form, when submitted hits my the Developer#toggle action with two parameters: act, which is either activate or deactivate and id which is the id of the Developer we are acting on. Both of these parameters are hidden fields within the form.
After the form is submitted, inside of the controller, I just obtain an instance of the correct Developer (in my case, doing so is rather complicated, but in most cases it's probably something like #dev = Developer.find(id)), and performs the steps necessary to activate/deactivate the developer.
Lastly, I created a toggle.js.erb file within the view directory for my Developer controller which gets rendered once the controller has completed its task. This file simply obtains the element (through the unique id we gave it in the partial) and replaces the inner html by re-rendering the partial as follows:
document.getElementById("<%=escape_javascript(#dev.id)%>").innerHTML="<%=escape_javascript(render :partial => 'developer/list_item', :object => #dev) %>";
The result is the partial being re-rendered after the developers active status has changed, resulting in the appropriate Activate or Deactivate button.
I realize that this answer is highly focused on my particular application, especially needing to deal with the toggling of active vs. inactive, however I believe it is easily simplified and adapted to other cases that may require less complexity.
You could do this task with Rails but then page refresh must be done. If you use Javascript/AJAX then page shouldn't be refreshed. Example (jQuery):
$(":button").click(function(){
//Your Code here..
});
EDIT:
Above is jQuery code. What you want to do is some action to happen when you click on button. If you want to call external API then you could use ajax. Look for documentation for POST and GET in jQuery: http://api.jquery.com/jQuery.post/ and http://api.jquery.com/jQuery.get/.
Google for rails jquery tutorial.
Assume, that I have a complex nested form with many fields.
I want to edit its fields one-by-one in ajax way, so that every time I see form - it is in 'show' style (without fields to change information), but with possibility to switch any particular field or group of fields to 'edit' mode with it's own 'save' or 'update' button.
Solving this kind of problem, I ended up with two ways:
Extended use of Ryan Bates' complex-form-examples.
The disadvantage of this way is that every field (or group of fields) requires it's own code (i.e. javascript: 'insert_fields'), which renders corresponding 'edit' style form, so it results in page is overwhelmed by javascripts.
Second - is unified procedure of loading corresponding edit partials through ajax through special controller action (i.e. get_partial), which "render :do updates" given field's area by 'edit' form.
For given field or group of fields i have partials for 'edit' and for 'show'. When i need to switch that field to edit mode i send request (link_to ...,'/.../get_partial?partial=foo',:remote => true) with necessary params by ajax, and some controller#action renders that partial by javascript.
I think that second approach is better one, but I can't figure out how optimize it the best.
Are there any more elegant solutions to this problem?
What if you generated a normal 'edit' form (with all the nested fields, etc), and then had the javascript hide the fields and add the text of the field and an edit link next to it. So for example say your form looks like:
= form_for #foo do |f|
= f.text_field :name
and your javascript would do this to it (1):
= form_for #foo do |f|
= f.text_field :name, :class => "hide"
<the value of the field here>
= link_to "edit", "#"
then make your javascript add a click event to the edit links that, when clicked, does:
= form_for #foo do |f|
= f.text_field :name
= f.submit "Save"
then you'd need more javascript that makes the save button submit the form (ajax), and go back to (1) above