Rails association - what controller code gets executed? - ruby-on-rails

I'm trying to make a change to some controller code. But, I don't understand where to put it.
I have the following in a new Contact form:
<%= f.association :location, :label_method => :name, :label => 'Location:' %>
I assumed that code would execute the index code in the location's controller.
But, I just deleted all of the code in the location index and the Contact form with the association to Location still has data in it.
I want the following code to execute at the Contact association stmt, but I don't know where to put it:
#locations = Location.ordered_by_ancestry_and(:name).map { |l| [" " * l.depth + l.name, l.id] }
UPDATE1
This is the development.log
Processing by ContactsController#new as HTML
Location Load (0.2ms) SELECT "locations".* FROM "locations" ORDER BY (case when locations.ancestry is null then 0 else 1 end), locations.ancestry, name
UPDATE2
I changed the ContactsController#new to this for testing:
# GET /contacts/new
# GET /contacts/new.json
def new
#locations = Location.first
And I still got all the locations in the select box.

The SimpleFormFor association helper method will generate a list (as a select list by default, but can be modified to radio or checkbox list) that acts as a nested attribute for that association. When submitted this field will be passed to the same controller action as the parent form - in this case the ContactsController#create action.
As far as your customized list of locations, you can pass this as a collection option to the association method:
<%= f.association :location, :collection => #locations, :label_method => :name, :label => 'Location:' %>
The actual place to build this #locations list would be in any action that may need to access it - that includes new and edit, as well as create and update (in case an error prevents your form from submitting). You can use a before_filter to eliminate any duplication.
The code may look something like:
class ContactsController < ApplicationController
before_filter :load_locations, :only => [:new, :edit, :create, :update]
#... actions go here
private
def load_locations
#locations = Location.ordered_by_ancestry_and(:name).map do |l|
[" " * l.depth + l.name, l.id]
end
end
end

Related

Limit scope on rails-jquery-autocomplete (rails3-jquery-autocomplete) gem - where clause issue

There is a recommended solution and it seems to work. The issue is in my where clause and I'm not sure what's wrong.
For reference, here is the solution(s):
https://stackoverflow.com/a/7250426/4379077
https://stackoverflow.com/a/7250341/4379077
I am trying to scope users that are members of the current_user's family tree memberships(branches) user's within my Nodes controller. This would normally be done using this code (current_user.family_tree.memberships).
Note I have successfully set this up to autocomplete showing all users (User.all):
In my routes:
resources :nodes do
get :autocomplete_user_first_name, :on => :collection
end
In my Node controller I have the following code:
autocomplete :user, :first_name, :extra_data => [:last_name, :email],
display_value: :full_name
And in my view I have the following form:
<%= form_for node do |f| %>
<%= f.label :user_tags %>
<%= f.autocomplete_field :user_tags, autocomplete_user_first_name_nodes_path, 'data-auto-focus' => true, value: nil %>
<%= f.submit %>
<% end %>
When I attempt to add the recommended solution to my nodes controller:
def get_autocomplete_items(parameters)
items = super(parameters)
items = items.where(:user_id => current_user.family_tree.memberships)
end
I get this message:
NoMethodError - super: no superclass method "get_autocomplete_items" for #<NodesController:0x007fc516692278>:
So, I found this article https://stackoverflow.com/a/18717327/4379077 and changed it to
def get_autocomplete_items(parameters)
items = active_record_get_autocomplete_items(parameters)
items = items.where(:user_id => current_user.family_tree.memberships)
end
It works, but I get the following error
PG::UndefinedColumn: ERROR: column users.user_id does not exist, so I changed the where clause to this :id => current_user.family_tree.memberships and I get this result
User Load (0.9ms) SELECT users.id, users.first_name, "users"."last_name",
"users"."email"
FROM "users" WHERE (LOWER(users.first_name) ILIKE 'mi%')
AND "users"."id" IN (SELECT "memberships"."id" FROM "memberships"
WHERE "memberships"."family_tree_id" = $1)
ORDER BY LOWER(users.first_name) ASC LIMIT 10 [["family_tree_id", 1]]
The issue is that I believe I need to get a collection within the membership model comparing the attribute membership.user_id to user.id. What am I doing wrong in my where clause?
Are Membership objects the same thing as Users?
if not, you need to get the user_id off the membership record
This line would need to change
# use pluck to get an array of user_ids.
items = items.where(:id => current_user.family_tree.memberships.pluck(:user_id))

Adding a value-dependent data attribute to a simple_form checkbox collection

I'm generating a list of checkboxes for a single collection like so:
= f.input :parts, as:check_boxes, collection: #parts_list
I want some checkboxes in the collection to disappear/reappear depending on the value of a select widget higher up in the form. (e.g. choosing "Tracker Robot" from the Robot select means that the "Legs" part checkbox disappears and the "Wheels" checkbox appears, etc.)
What I'd like to do is attach a computed data attribute to each individual Part checkbox, with the attribute value listing the Robots that can use that Part; then some JS will do the work of hiding/showing the checkboxes. However, I don't know how I can generate those data attributes using simple_form.
I would normally create a custom "parts" input, but there seems to be a problem with making custom collection inputs; it looks for a named method (collection_parts) inside form_builder.rb, which won't exist, and if I try and extend the FormBuilder it sends me down a major rabbit hole.
I could write some JS to load the data attrs into the generated HTML, but then I have to generate custom JS based on my Rails data, and that feels like the wrong way to do it.
Let's assume that the form is for Order model and you are changing the parts collection based on the value of a field called region.
Update the form view. Specify the id for form, region field and parts field.
= simple_form_for(#order, :html => { :id => "order-form"}) do |f|
= f.input :region, :wrapper_html => { :id => "order-form-region", |
"data-parts-url" => parts_orders_path(:id => #order.id, :region => #order.region)} |
= f.input :parts, as: check_boxes, collection: #parts_list, |
:wrapper_html => { id' => 'parts-check-box-list'} |
Add a new action called parts in the route.rb file.
resources :orders do
collection do
get :parts
end
end
Add the new action to your controller
class OrdersController < ApplicationController
# expects id and region as parameters
def parts
#order = params[:id].present? ? Order.find(params[:id]) : Order.new
#parts_list = Part.where(:region => params[:region])
end
end
Add a helper
def parts_collection(order, parts_list)
"".tap do |pc|
# to generate the markup for collection we need a dummy form
simple_form_for(order) do |f|
pc << f.input(:parts, as: check_boxes, collection: parts_list,
:wrapper_html => {:id => 'parts-check-box-list'})
end
end
end
Add a js view for the action (orders/parts.js.erb)
$('#parts-check-box-list').replaceWith('<%= j(parts_collection(#order, #parts_list)) %>');
Register data change event handlers for region field in your application.js
$(document).ready(function() {
$('#order-form').on("change", "#order-form-region", function () {
// Access the data-parts-url set in the region field to submit JS request
$.getScript($(this).attr('data-parts-url'));
});
});
I think you can do it like this:
= f.input :parts do
= f.collection_check_boxes :parts, #parts_list, :id, :to_s, item_wrapper_tag: :label, item_wrapper_class: :checkbox do |b|
- b.check_box(data: { YOUR DATA ATTRIBUTES HERE }) + b.text
this may be simpler.
Assumptions
#robots - an array containing the list of robots
#parts - a hash containing a list of parts for each robot
Sample Code
# controller
#robots = %w[tracker nontracker]
#parts = { tracker: %w[wheels lcd resistor], nontracker: %w[lcd resistor] }
# view
= f.input :robots, as: :select, collection: #robots, input_html: { id: 'robot-select' }
#parts-list
:javascript
var parts = #{#parts.to_json};
$(document).ready(function() {
$('#robot-select').change(function() {
$('#parts-list').html('');
$(parts[$(this).val()]).each(function(index, text) {
$('#parts-list').append('<input type="checkbox" value=' + text + '>' + text + '</input>')
})
})
})
you can see this working if you clone https://github.com/jvnill/simple_form_search_app and go to /robots
Some input options in SimpleForm accept a lambda that gets called for every item in a collection:
f.input :role_ids, :collection => (1..10).to_a,
:label_method => :to_i, :value_method => :to_i,
:as => :check_boxes, :required=> true,
:disabled => ->(item){ item.even? }
but input_html doesn't seem to be one of them.
The solution is probably to create a custom SimpleForm collection input that applies the data attributes itself. Not as flexible perhaps, but I think this is the only way to go for now.
There's a tutorial page on GitHub that should get you started.

Dynamic url_path in each loop

I have a city page with a jQuery content carousel. The content of the carousel is filed by a each loop.
CityController
#events = #city.events.find_all_by_hot(true)
#activities = #city.activities.find_all_by_hot(true)
#sights = #city.sights.find_all_by_hot(true)
#hot = #events + #activities + #sights
Class city
has_many: events
end
class events
belongs_to :city
has_many :attachments, :as => :attachable
accepts_nested_attributes_for :attachments
end
Activities and sights models are the same
City view content slider:
#hot.each do |a|
a.attachments.each do |a|
= image_tag(a.file.url, :height =>"325px", :width =>"650px" ), url_path
I want to generate links (url_path) in my each loop...how can I realize this? It cannot place the url_path of the routes because they are dymanic based on which attachment (image) is loaded.
Although your syntax of image_tag is incorrect you can try this
#hot.each do |hot|
hot.attachments.each do |a|
link_to polymorphic_path(a.attachable) do
image_tag(a.file.url, :height => "325px", :width => "650px")
end
end
end
If I understand your problem correctly. Also check out the polymorphic_path helper, which is what you need.
Am I right that the link should point to the a which can be any of events, activities, sights? as
#hot = #events + #activities + #sights
I would try creating a special controller action in the CityController
def hottie
#duck = Kernel.const_get(params[:type]).find_by_id(params[:id])
redirect_to #duck
end
then add something like
match 'hottie/:type/:id' => 'city#hottie', as: 'hot'
which should give you a path helper that you can use as this:
<%=link_to("Open", hot_path(a.class.to_s, a.id)) %>
Addition: this is of course a bit dirty and needs some security things considered (e.g. limit it to show only special types). You could also consider moving the three classes Event, Activities and Sights into a Object Hierarchy using STI; that should eliminate the need of passing the type in the request.

Rails Craiglist like list of items

So I have a state model and city model associated with has_many and belongs_to. I want to display a page with each state with its associated cities underneath.
I created a page controller and page called "Locations" and manually entered in
<%= link_to "Allentown", allentown_path %>
which then takes you to the allentown page.
On the allentown page I filtered the listings by adding this code to the pages controller
def allentown
#title = "Allentown Listings"
#tattoo_briefs = TattooBrief.where( :city_id => 1 ).find(:all, :order => "id DESC" )
end
I know this isn't DRY. Also can get very cumberson if I have 200 cities. Is there a better way?
You need to add a resource to your routes:
routes.rb
resources :city
That essentially gives you all the RESTful actions for the City model. Then, in your controller, use the show action to..wait for it..show your city page
cities_controller.rb
def show
#city = City.find(params[:id])
#title = "#{#city.name} Listings"
#tattoo_briefs = TattooBrief.where( :city_id => params[:id] ).find(:all, :order => "id DESC" )
end
you can still modify this by studying more on routes and controllers from the rails api. With added knowledge, you can get to allentown by modifying your route to use the city name instead of the id.

Helper method or Model or Controller?

Say I have a form_for with a select menu to assign a User on a belongs_to association:
...
form.select :user_id, #users, :prompt => "Select a User"
...
Currently I have #users in the controller as follows:
#users = User.all.map { |u| [u.full_name, u.id] }
I feel like this logic should maybe moved into a helper or even to the model.
But I am confused as to where to deal with this and how.
The general answer depends on how often you're going to use it:
helper: used often but only in views or controllers
model: used often anywhere the model can be used (other models)
controller: used rarely and only for specific actions.
However in your case, the answer is none of the above, and stop trying to reinvent the wheel. About 95% of the things people try to do with Rails are tasks that others have already done. There's a very good chance that it's either in the Rails Core or exist in either gem or plugin form.
What you're trying to do has already been done, and is built into the Rails core. It's a ActionView::Helpers::FormOpitionsHelper method called collection_select
collection_select does exactly what you want to do, it's also much more robust than a single purpose method.
It has the form of
collection_select(object, method, collection, value_method,
text_method, select_options = {}, html_options)
value_method and text_method are sent to each item in collection to get the select value and the display text for each select option. It is not required that either are column names.
Use it like this:
<% form_for #whatever do |form| %>
<%= form.collection_select :user_id, User.all, :id,
:full_name, :prompt => "Select a User" %>
<% end %>
You should put this in a model as it's logic oriented and by the way you should never do
#users = User.all.map { |u| [u.full_name, u.id] }
but
#users = User.all(:select => "full_name, id")
and if full_name is a method, something like that :
#users = User.all(:select => "last_name, first_name, id").map{|u| [User.full_name(u.first_name, u.last_name), u.id]}
That would be a model method since it has logic for the model. Helper methods should have UI level logic (whether to display a link or not) and HTML helpers (methods to generate links for example)
i think moving that to a helper is the best thing, since it just does help you with creating options for a select box, which is UI level.
But unless you would use that piece of code again, then to the model it must go! :)
The model:
def self.select_display
all(:select => "id, first_name, last_name").map { |u| [u.name, u.id] }
end
The view:
select :user_id, User.select_display
I had a similar problem and ended up using a module, in order to stay as DRY as possible (most of my models had a name and an id)
The module looked like this:
#lib/all_for_select.rb
module AllForSelect
def all_for_select(permission = :read)
#used declarative authorization for checking permissions
#replace first line with self.find(:all, if not using it
with_permissions_to(permission).find( :all,
:select =>"#{table_name}.id, #{table_name}.name",
:order => "#{table_name}.name ASC"
)
end
end
On your model, you just extend the module:
class Client < ActiveRecord::Base
extend AllForSelect
...
end
On your controller, you can call Client.all_for_select. I usually do this on a before_filter
class SalesController < ApplicationController
before_filter :fill_selects, :only => [:new, :edit, :update, :create]
...
private
def fill_selects
#clients = Client.all_for_select
end

Resources