Rails: Passing new child object placeholder (build) to parent view - ruby-on-rails

I've got 2 classes of objects... Magician has_many Rabbits and Rabbit belongs_to Magician.
When viewing a Magician (show.html) I'd like to list all the associated Rabbits and then have some blank fields with a submit button to add a new Rabbit. To do so I build a new rabbit (associated to the current magician) in the Magician's show method (below).
Edit2: found way to make it work but not sure if it's the "Rails way"?
see comments inline (below):
If I build the rabbit in Magician's show method then when show is rendered an empty (and invalid) rabbit ends the list before the new rabbit form fields are then shown.
If I build it in the view itself then everything works & renders correctly. I was led to believe that we should not be doing this type of stuff in the view...if so, what's the proper way to address this?
#/app/controllers/magicians_controller.rb
class MagiciansController < ApplicationController
respond_to :html, :json
def show
#magician = Magician.find(params[:id])
#rabbit = #magician.rabbits.build # <=== build here and empty rabbit gets
# included in #magician.rabbits when they're rendered...
# but is NOT included in #magician.rabbits.count for some reason?!?!?
respond_with(#magician)
end
...
end
#view/magicians/show.html.haml
%p
%b Name:
= #magician.name
%h2 Rabbits
= "There are #{pluralize(#magician.rabbits.count, "rabbit")}"
= render #magician.rabbits, :target => #magician
%h2 Add a rabbit:
- #rabbit = #clown.rabbits.build -# <=== build here and everything SEEMS to work
= render :partial =>"rabbits/form", :locals => { :parent => #magician, :foreign_key => :magician_id, :flash => flash }
Edit1: Adding generated html from partial as per request:
<p>
<b>Rabbit:</b>
<b>Color:</b>
|
<b>ID:</b>
Kill Rabbit
</p>
And I suppose you probably want to see the partial that generates this:
%p
%b Rabbit:
= rabbit.name
%b Color:
= rabbit.color
|
%b ID:
= rabbit.id
= link_to("Kill Rabbit", [target, rabbit], :method => :delete, :confirm => "Sure? A bunny will die")

Related

Render object attributes on the same site with click

There's this button / link in my web app. It's a button for a pizza - margherita with price, ingredients, and category, all already loaded to the db. Clicking this button loads the item's id to params, which is then used to find the object.
I want the click to launch the following chain of events:
Load the id
Find the object by id
Instantly render the object and its attributes in adjacent div on the same site.
Is this possible without JavaScript, just Ruby on Rails?
I am stuck at point 3. I have an idea, but incomplete. Please, give me a hint.
PS. My idea is to render views for index and show in separate divs next to one another.
#NickM, I added your code and it's throwing ActionController::UnknownFormat error, pointing at respond_to do |format| in show_pizza method.
Here's my pizzas/index.html.haml:
%h1= t :all_pizzas
- #pizzas.each do |pizza|
= render pizza
.orders
%h1 Orders
And pizzas/_pizza.html.haml:
%tr
= link_to pizza.name, show_a_pizza_path(pizza.id), :method => :post
%br/
I am getting this error upon clicking the link in _pizza.html.erb. My show_pizza.js is now:
pizza_div = $("#orders");
pizza_div.html( "<%= j render( :partial => 'pizzas/pizza', :locals => { :pizza => #pizza } ) %>" );
What am I missing? Sorry, I don't know js.
The short answer is no. When the link is clicked you will need to either render another page or handle the data returned by the controller with Javascript. The best option would be to set up an action that responds with JSON and parse what you get back on the front end.
Even after your P.S. you will have to handle the response from the server with Javascript, unless you are okay with rendering a new page. You need to post some code, preferably routes and controller action. This would also work:
<%= link_to("Show Pizza", show_a_pizza_path(#pizza.id), :method => :post, :data => {:remote => true}) %>
routes.rb
post 'show_pizza/:id' => "pizzas#show_pizza", :as => :show_a_pizza
pizzas_controller.rb
def show_pizza
#pizza = Pizza.find(params[:id])
respond_to do |format|
format.js
end
end
views/pizzas/show_pizza.js
pizza_div = $("#the_name_of_your_div");
pizza_div.html( "<%= j render( :partial => 'pizzas/pizza', :locals => { :pizza => #pizza } ) %>" );
and then put the pizza markup in /views/pizzas/_pizza.html.erb

Create a link for next page in rails

I'm using the Twilio API in a rails app to show a user a list of their recordings. Say a user has 11 recordings total, and I'm showing them 3 per page.
twilio_controller.rb
def calls
#user = current_user
#account_sid = #user.twilio_account_sid
#auth_token = #user.twilio_auth_token
#page_size = 3
#page = params[:page_id] || 0
#sub_account_client = Twilio::REST::Client.new(#account_sid, #auth_token)
#subaccount = #sub_account_client.account
#recordings = #subaccount.recordings
#recordingslist = #recordings.list({:page_size => #page_size, :page => #page})
end
calls.html.erb
<% #recordingslist.each do |recording| %>
<tr>
<td><%= recording.sid %></td>
</tr>
<% end %>
<%= link_to "Next Page", twilio_calls_path(#page + 1) %>
routes.rb
#twilio routes
post 'twilio/callhandler'
get 'twilio/calls'
match 'twilio/calls' => 'twilio#page', :as => :twilio_page # Allow `recordings/page` to return the first page of results
match 'twilio/calls/:page_id' => 'twilio#page', :as => :twilio_page
Paging info is built into the Twilio response such that
#recordingslist.next_page
gives me the next 3 recordings (verified in rails console). How do I link to that so that when a user clicks the link, the table loads the next 3 results?
Thanks!
You can use a gem like Kaminari for Pagination.
https://github.com/amatsuda/kaminari
I would recommend utilizing the paging functionality that ships with twilio-ruby. According to the docs:
ListResource.list() accepts paging arguments.
Start by create a route for your Twilio list view. Make sure you can pass a page_id parameter – this is how your controller action will know which page to render:
# config/routes.rb
match 'recordings/page/:page_id' => 'twilio#page', :as => :twilio_page
match 'recordings/page' => 'twilio#page', :as => :twilio_page # Allow `recordings/page` to return the first page of results
Then, in the page action, pull the page_id parameter (or set if to 1 if there is no page_id, and pass the page_number and page_size as arguments to #recordings.list:
# app/controllers/twilio_controller.rb
def page
page_size = 3
#page = params[:page_id] || 1
#sub_account_client = Twilio::REST::Client.new(#account_sid, #auth_token)
#subaccount = #sub_account_client.account
#recordings = #subaccount.recordings
#recordingslist = #recordings.list({:page_size => page_size, :page => page)})
end
Finally, in your view, pass the page number to twilio_page_path in your link_helper – just make sure to adjust the page number accordingly (+1 for the next page, -1 for the previous page:
# view
<%= link_to "Next Page", twilio_page_path(#page.to_i + 1) %>
<%= link_to "Previous Page", twilio_page_path(#page.to_i - 1) %>
Note that – if you're at the start or end of your list – you may inadvertently end up passing an invalid page_id. Therefore, you may want to implement some exception handling in your page controller action:
# app/controllers/twilio_controller.rb
def page
begin
#page = params[:page_id] || 1 # If `page_id` is valid
rescue Exception => e
#page = 1 # If `page_id` is invalid
end
# Remaining logic...
end

How to use jquery-Tokeninput and Acts-as-taggable-on

This is how you use autocomplete with jQuery Tokeninput and ActsAsTaggableOn.
In my situation i am using a nested form but it shouldnt matter. Everything below is code that works.
Code
Product Model:
attr_accessible :tag_list # i am using the regular :tag_list
acts_as_taggable_on :tags # Tagging products
Products Controller:
#1. Define the tags path
#2. Searches ActsAsTaggable::Tag Model look for :name in the created table.
#3. it finds the tags.json path and whats on my form.
#4. it is detecting the attribute which is :name for your tags.
def tags
#tags = ActsAsTaggableOn::Tag.where("tags.name LIKE ?", "%#{params[:q]}%")
respond_to do |format|
format.json { render :json => #tags.map{|t| {:id => t.name, :name => t.name }}}
end
end
Routes:
# It has to find the tags.json or in my case /products/tags.json
get "products/tags" => "products#tags", :as => :tags
Application.js:
$(function() {
$("#product_tags").tokenInput("/products/tags.json", {
prePopulate: $("#product_tags").data("pre"),
preventDuplicates: true,
noResultsText: "No results, needs to be created.",
animateDropdown: false
});
});
Form:
<%= p.text_field :tag_list,
:id => "product_tags",
"data-pre" => #product.tags.map(&:attributes).to_json %>
Issue 1(SOLVED)
Must have the line:
format.json { render :json => #tags.collect{|t| {:id => t.name, :name => t.name }}}
Note - You can use #tags.map here as well and you dont have to change the form either.
Below are the 2 issues on why you needed to do this:
I have the following Tag: {"id":1,"name":"Food"}. When I save a Product, tagged "Food", it should save as ID: 1 when it searches and finds the name "Food". Currently, it saves a new Tag with a new ID that references the "Food" ID, i.e. {"id":19,"name":"1"}. Instead, it should be finding the ID, showing the name, and doing a find_or_create_by so it doesn't create a new Tag.
Issue 2(SOLVED)
When I go to products/show to see the tags by doing <%= #product.tag_list %>. The name appears as "Tags: 1", when it really should be "Tags: Food".
How can I fix these issues?
You should define a route in your routes.rb which should handle products/tags path. You can define it like:
get "products/tags" => "products#tags", :as => :tags
Thus should give you a tags_path helper which should evaluate to /products/tags. This should get rid of the errors you mentioned in the question. Be sure to add this route before defining resources :product in your routes.rb
Now onto acts-as-taggable-on, I haven't used this gem, but you should look at method all_tag_counts documentation. Your ProductsController#tags method will need some changes on the following lines. I am not sure if its exactly what would be required, as I use Mongoid and can't test it out.
def tags
#tags = Product.all_tag_counts.(:conditions => ["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", "%#{params[:q]}%"])
respond_to do |format|
format.json { render :json => #tags.collect{|t| {:id => t.name, :name => t.name } }
end
end
little add-on:
If you want to create the tags on the fly, you could do this in your controller:
def tags
query = params[:q]
if query[-1,1] == " "
query = query.gsub(" ", "")
Tag.find_or_create_by_name(query)
end
#Do the search in memory for better performance
#tags = ActsAsTaggableOn::Tag.all
#tags = #tags.select { |v| v.name =~ /#{query}/i }
respond_to do |format|
format.json{ render :json => #tags.map(&:attributes) }
end
end
This will create the tag, whenever the space bar is hit.
You could then add this search setting in the jquery script:
noResultsText: 'No result, hit space to create a new tag',
It's a little dirty but it works for me.
There is a bug in Application.js code. There is an extra ) after "/products/tags.json". Remove the extra ). The code should be:
$("#product_tags").tokenInput("/products/tags.json", {
prePopulate: $("#product_tags").data("pre"),
preventDuplicates: true,
noResultsText: "No results, needs to be created.",
animateDropdown: false
});
I don't know if this is the entirety of your error, but you are not hitting the proper URL with the tokenInput plugin.
This
$("#product_tag_list").tokenInput("/products/tags.json"), {
should be
$("#product_tag_list").tokenInput("/products.json"), {
As I said, I don't know if this is the only problem you are having, but if you change this, does it work?
EDIT:
I have never used ActsAsTaggableOn. Does it create a Tag model for you to use?
From the looks of it on github, if you wanted to query all tags, you might have to use its namespace as opposed to just Tag, meaning ActsAsTaggableOn::Tag. For example, you can see how they access Tags directly in some of the specs.
I had problems with editing the tags if for example the model failed to validate,
I changed
<%= p.text_field :tag_list,
:id => "product_tags",
"data-pre" => #product.tags.map(&:attributes).to_json %>
to
<%= p.text_field :tag_list,
:id => "product_tags",
"data-pre" => #product.tag_list.map {|tag| {:id => tag, :name => tag } }.to_json %>
If the form failed to validate on first submission, it was creating tags as the ID's of the tags it had created on subsequent submissions.
Two notes: if you're getting the tags changed by numbers on the POST request, use:
tokenValue: "name"
And if you're trying to add non-existent tags, use (undocumented):
allowFreeTagging: true

How to update partial after completing job

This is my first post here but I have gotten some great info from this site already. So I thought someone may be able to help.
In my view I have a form that once submitted, the controller passes the job off to navvy and it really works well. The problem is I would like to have another partial on the same page as the form update with the new info once navvy is done working. So in my controller I have:
Navvy::Job.enqueue( GetZip, :get_zip, #series, :job_options => {:priority => 8})
And then in my navvy block which is located in config/initializers/navvy.rb I have:
class GetZip
def self.get_zip(params)
fetch = Fetch.new
fetch.get_zip(params)
# What to put here to update partial #
end
end
Which works as expected. I am just not sure how I can have the partial in my view updated once the navvy job is completed. Any suggestions would be greatly appreciated.
The problem here is that once you've fired up a background process you're no longer tied into that user's request (which is obviously the point of the background process).
So your background job is not aware of what the user is doing in the meantime. For example they could navigate to another page, or even leave your website.
You could get your background job to create a record in a database that indicates it has started processing and update this status when it has finished processing. That way when the user next refreshes the page you can check the status of that record and the partial can be updated accordingly.
If you want the page to auto-update you could keep polling the status of that record with ajax requests. On the initial submission of the form you could start the polling rather than have it running all the time.
Here's what I'm using.
This is where the Job is called:
Setting::progress = "Starting..."
Navvy::Job.enqueue(EmailWorker, :async_email, stuff)
Settings is super simple:
class Setting < ActiveRecord::Base
def Setting::progress=(value)
setn = Setting.find_or_initialize_by_name("email_status")
setn.value = value
setn.save
end
end
and the navvy job EmailWorker is:
class EmailWorker
def self.async_email(options)
# send one at a time
total = options[:list].length
errors = []
options[:list].each_with_index do |email_addr, n|
begin
Setting::progress = "#{n+1}/#{total}: #{email_addr}"
Postoffice.one_notice(email_addr, options).deliver
rescue Exception => e
Setting::progress = "#{email_addr} #{e.message}"
errors << "<span class='red'>#{email_addr}</span> #{e.message}"
end
# get stack level too deep errors at random when this is removed. I don't get it.
sleep 0.05
end
Setting::progress = "DONE sending #{total} with #{errors.length} errors<br/>#{errors.join("<br/>")}"
"Done" # just for display in Navvy console output
end
end
Then the triggering page has this:
<%-
# had to add this back as rails 3 took it away
def periodically_call_remote(options = {})
frequency = options[:frequency] || 10 # every ten seconds by default
code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
javascript_tag(code)
end
-%>
<script type="text/javascript">
//<![CDATA[
check_var = true;
//]]>
</script>
<%= periodically_call_remote(
:condition => "check_var == true",
:url => { :controller => "application",
:action => "update" },
:frequency => '3') -%>
<div id="progress">
<p>Monitoring progress of emails</p>
</div>
And heres the method that's called repeatedly:
def update
raise "not for direct" if (!request.xhr?)
#progress = Setting::progress
#again = !(/^DONE/ =~ #progress)
render :action => "update"
end
Which trigers an in-place update via update.rjs
page.assign('check_var', #again)
page.replace_html "progress", :partial => "info", :locals => { :info => #progress }
page.visual_effect :highlight, 'progress', :startcolor => "#3333ff", :restorecolor => '#ffffff', :duration => 0.5
One word of warning about Navvy - since it runs as a background task until killed, it will stay executing your old app when you cap deploy. You must kill navvy in old "current" and move to new "current".

Rails Beginner - How to define 3 buttons enabling a user to make a choice?

I am aware this is a very basic question, we are very new to rails and have been unable to find a specific answer to this question.
Background: We have just 1 database containing product information (called Product), one column (type) contains information regarding the product type and is either a value of 1 or 2.
Aim: create 3 buttons on a page which correspond to different user choices
e.g. Button 1 - show items of type 1; Button 2 - show items of type 2; Button 3 - show all items.
Ideally the information regarding the button pressed should be visible to a number of pages within a class (we have an index page, as well as 3 others in the controller)
Would somebody be able to provide an outline of the code required to do this please?
I am guessing it is some combination involving the ..._controller.rb and..._helper.rb?
Thanks a lot for your patience
What I would do is the following.
First, create a scope or named_scope in your Project model for finding projects by type. You'll then be able to use this scope to query your projects depending on type.
# Rails 3
class Project
scope :by_type, lambda{ |type| where(type: type.to_i) unless type.nil? }
end
#Rails 2
class Project
named_scope :by_type, lambda do |type|
{ :conditions => { :type => type.to_i } } unless type.nil?
end
end
Next, create a before filter in your controller to load the projects of that type. The before filter should be applied to all pages where you want the buttons to be present:
class ProjectsController
before_filter :load_projects, :only => [:index, :action1, :action2]
protected
def load_projects
#projects = Project.by_type(params[:type])
end
end
Finally, create a partial for the buttons that you can include in the views that have the option of displaying different project types:
# _project_options.html.erb
<%= link_to "Button 1", :controller => params[:controller], :action => params[:action], :type => '1' %>
<%= link_to "Button 2", :controller => params[:controller], :action => params[:action], :type => '2' %>
<%= link_to "Button 3", :controller => params[:controller], :action => params[:action], :type => '' %>
You can then include this partial in each of your related views. And you'll be able to display the projects by doing something like this (if you have an _projects.html.erb partial defined):
render #projects
You can load all the Products and then hide them selectively with some javascript. Just add a class to your markup for each type of product, like this:
<%= link_to #product.name, product_path(#product), :class => #product.type %>

Resources