I'm building an application (web/iOS) that allows a user to set a series of preference options. The models/tables required include:
Users: stores user name, password, email address
Prefs: stores names/classes of preferences available, i.e., "sex"
PrefOpts: stores options for each preference, i.e., "Male" and "Female" for sex
UserPrefs: stores a selected PrefOpt for each User for each Pref
Model Specs:
class User < ActiveRecord::Base
has_many :user_prefs
has_many :prefopts, through: :user_prefs
end
class Pref < ActiveRecord::Base
has_many :prefopts
has_many :user_prefs
accepts_nested_attributes_for :prefopts
validates :name, presence: true
end
class Prefopt < ActiveRecord::Base
belongs_to :pref
has_many :user_prefs
has_many :users, through: :user_prefs
accepts_nested_attributes_for :user_prefs
end
class UserPref < ActiveRecord::Base
belongs_to :user
belongs_to :prefopt
end
For now, I want to set the user's preferences/options on the user "show" page, so when I pull up a user's record, I see a listing of all the preferences and for each a drop-down list of the available preferences for each option.
I have updated the Users controller to query back the preferences...
# GET /users/1
# GET /users/1.json
def show
#prefs = Pref.all
end
Also, I added to the routes file references under users:
resources :users do
resources :prefs do
get 'prefopts', on: :member
end
end
And this works fine: on a user's "show" page I can see all the available preferences when using this syntax:
<p>
<H2>Preferences</H2>
<ul>
<% #prefs.each do |pref| %>
<li><%= pref.name %></li>
<ul>
</ul>
<% end %>
</ul>
</p>
But when I add the code to loop over each "prefopt" for each pref, I get an error.
<p>
<H2>Preferences</H2>
<ul>
<% #prefs.each do |pref| %>
<li><%= pref.name %></li>
<ul>
<% #pref.prefopts.each do |prefopt| %>
<li><strong>Option: </strong><%= prefopt.name %></li>
<% end %>
</ul>
<% end %>
</ul>
</p>
Error message:
undefined method `prefopts' for nil:NilClass
Now, I've updated the pref scaffolding's show page to allow me to add and list prefopt records for each preference, and I'm using the same syntax from that view here.
If I take the error message at face value, it looks as if it thinks the "pref" is nil, but if that's the case why is the pref's name showing up correctly before I add in the prefopt?
Is there something I need to do in the view to pre-populate each pref's options? Or am I going about this all wrong? It there a best practice that I haven't found yet?
I've done extensive searching and have found some tips on working with many-to-many relationships, including:
http://www.createdbypete.com/articles/working-with-nested-forms-and-a-many-to-many-association-in-rails-4/
I can see how I can save one user record sending a long a bunch of nested attributes, if only I can get the options to display.
I've also searched for best practices in saving user preferences and none of the examples I've found allow for the flexibility to dynamically add user preferences in the future by storing them in a separate model/table. I'm tempted to simply create one table for each preference and one join table for each user and each preference, but that's not a DRY approach. I can see how to save these nested attributes, if I can only list the options available to the user for each preference.
Thanks for any thoughts on this!
The error message you receive is because #pref is not set.
Rewrite your view like this:
<% #prefs.each do |pref| %>
<li>
<%= pref.name %>
<ul>
<% pref.prefopts.each do |prefopt| %>
<li><strong>Option: </strong><%= prefopt.name %></li>
<% end %>
</ul>
</li>
<% end %>
(Note I removed the #-sign before pref on line 5)
Related
I started writing an app which reviews books, articles etc(model name: Piece ) and has the option to review chapters or smaller sections (Sections, Subsections, and Subsubsections models)
I want to be able to generate a general overview of the Piece where all of the nested Sections, Subsections, and Subsubsections and their attributes will be listed in one view. So my routes.rb file looks like this:
resources :pieces do
resources :sections do
resources :subsections do
resources :subsubsections
end
end
end
Which I have since learned is sloppy but its too late for me now.
For now, I have been able to do this from the subsubsections, because it has acces to all the parent parameters (piece_id, section_id, etc). But this means that I have to jump down the tree into the Subsubsection view and then go from there, whereas I would like to be able to access all these parameters from the Piece controller so that I can link straight from the Piece view to the more general overview, without having to go all the way down the chain.
I apologize if this is a simple question, I only started recently.
My models like like this:
class Subsubsection < ActiveRecord::Base
belongs_to :subsection
end
class Subsection < ActiveRecord::Base
belongs_to :section
has_many :subsubsections
end
class Section < ActiveRecord::Base
belongs_to :piece
has_many :subsections
end
class Piece < ActiveRecord::Base
has_many :sections
has_many :links
end
And the way i am accessing the general overview for now is using the index action of the subsubsections controller:
def index
#piece = Piece.find(params[:piece_id])
#section = #piece.sections.find(params[:section_id])
#subsection = #section.subsections.find(params[:subsection_id])
end
My view accesses it like this:
<p><%= link_to "'General Piece Overview", piece_section_subsection_subsubsections_path(#piece, #section, #subsection), class: 'section_name' %></p>
Heres the link to the project in case it helps:
https://github.com/kingdavidek/StuddyBuddy
Your question has some internal tension; you say you want access to the nested parameters from your PieceController, but if the request has been routed to PieceController, those nested parameters will not be present. When nested parameters are present, the request will be routed to the most specific controller class for which the request provides a parameter.
If the question can be rephrased as "how, from within PieceController can I generate something like a table of contents?" the answer looks something like this.
In PieceController:
def show
#piece = Piece.find(params[:id]).include(sections: {subsections: :subsubsections}})
end
In app/views/pieces/show.html.erb
<% #piece.sections.each do |section| %>
<%= link_to piece_section_path(#piece, section), section.title %>
<% section.subsections.each do |subsection| %>
<%= link_to piece_section_subsection_path(#piece, section, subsection), subsection.title %>
<% subsection.subsubsections.each do |subsubsection| %>
<%= link_to piece_section_subsection_subsubsection_path(#piece, section, subsection, subsubsection), subsubsection.title %>
<% end %>
<% end %>
<% end %>
<% end %>
This makes some assumptions, but hopefully the idea is clear.
I'm trying to figure out how to list my users by a certain string. In my app I have Artist who have one Artist_profile through has_one. In my Artist_profile, I have genre, which is what I'm trying to sort them out by. I do have it working at the moment with an if statement, but it looks through every single Artist and picks out the ones with the match. For example:
<% #artists.each do |artist| if artist.artist_profile.genre == "Rock" %>
<li><%= artist.id %></li>
<% end %>
I'm trying to get it something more like this, so it's less strain on my database:
<% #artists.rock.each do |artist| %>
<li><%= artist.id %></li>
<% end %>
and my model:
def self.rock
where(Artist.artist_profile.genre == "Rock")
end
However I get this error:
undefined method `artist_profile' for #<Class:0x6962940>
I think it's something to do with the has_one method, but can't seem to get this to work no matter what I try.
You should use the Joins method. It allows you to search fields in associated models. This differs from the Includes method as the Joins method doesn't include the results in the data. For example, if you wanted to list the artist genre instead of the id, then you would want to use Includes. In your code, you should put:
#artists = Artist.joins(:artist_profile).where("artist_profiles.genre" => "Rock")
You cannot call scope as Artist.artist_profile, it's an instance method
You can defind scope rock in ArtistProfile model
The result will look like this
class ArtistProfile < ActiveRecord::Base
belongs_to :artist
scope :rock, ->{where(genre: "Rock")}
...
and use it:
#rock_profiles = ArtistProfile.rock
<% #rock_profiles.each do |profile| %>
<li><%= profile.artist_id %></li>
<% end %>
I'm new to rails. I've searched and been stuck on this problem for a couple days now.
I am building a site where there are users, bands (that users can join), tours (that belong to bands), and stops (stops on the tours). I have tables for each, as well as additional tables that link them together by id (bandmembership, bandtourmembership, tourstopmembership).
I followed a few tutorials and have used belongs_to, has_many => through to link these all together and I have used nested attributes to display data from one level deep successfully.
The final format I'm trying to display is
User Name
=> Band Name #1
====> Tour Name #1
========> Tour Stop #1
========> Tour Stop #2
====> Tour Name #2
========> Tour Stop #1
========> Tour Stop #2
=> Band Name #2
====> Tour Name #3
========> Tour Stop #1
========> Tour Stop #2
etc.
Currently I only can get the band name to display without an error, but it displays the same band name 3 times (there are 3 in the database). When I try to add in tours it just gives an error. I'd also like to try to use a partial and a collection to break out the rendering of each time of item.
My questions are
Why is the partial displaying the same name 3 times and how do I get it to display the correct name?
Why am I not able to access tours from bands and how do I get it to cooperate?
views/users/show.html.erb
<h1>Dashboard</h1>
<%= render partial: 'shared/user_item' %>
<% if #user.bands.any? %>
<h2>You are in <%= #user.bands.count %> bands:</h2>
<%= render partial: 'shared/band_item', collection: #band_items %>
<% else %>
shared/_band_item.html.erb
<%= #band.name %>
shared/_tour_item.html.erb
<%= #tour.name %>
shared/_stop_item.html.erb
<%= #stop.name %>
controllers/users_controller.rb
class UsersController < ApplicationController
def create
#user = User.new(user_params)
if #user.save
sign_in #user
flash[:success] = "You are now signed in"
redirect_to #user
else
render 'new'
end
end
def show
#user = User.find(params[:id])
#band_items = Bandmembership.where(user_id: #user.id)
#band = Band.find(params[:id])
end
def new
#user = User.new
end
private
def user_params
params.require(:user).permit(:firstname, :lastname, :email, :password, :password_confirmation)
end
end
models/user.rb
class User < ActiveRecord::Base
has_many :bandmemberships
has_many :bands, :through => :bandmemberships
has_many :tours, :through => :bands
accepts_nested_attributes_for :bands, :tours
end
models/bandmembership.rb
class Bandmembership < ActiveRecord::Base
belongs_to :user
belongs_to :band
end
models/tour.rb
class Tour < ActiveRecord::Base
has_many :tourstopmemberships
has_many :stops, :through => :tourstopmemberships
has_many :bandtourmemberships
has_many :bands, :through => :bandtourmemberships
accepts_nested_attributes_for :stops
end
This smells like something solvable by a class-level delegate method (Rails Antipatterns, pp 6-7)
You've got the show method pulling the params for both user and band. Is that something like tld.com/user/1/band/3?
If you don't have params for both in the ID, then it's pulling the user's ID for band or vice versa.
From a code perspective, you should be able to refactor towards something like this:
<h1>Dashboard</h1>
<%# this should render app/views/users/_user.html.erb %>
<%= render #user %>
<% if #user.bands %>
<h2>
You are in <%= #user.bands.count %> bands:
</h2>
<ul>
<%= #user.bands.each do band %>
<%# this should render app/views/bands/_band.html.erb %>
<%= render #band,
locals: (band: band) %>
<% end %>
</ul>
<%- end -%>
Your _band.html.erb would be
<li class="band-name">
<%= band.name %>
</li>
The code may not be 100% right as I keep jumping between a 2.3 app and a 4.x app. But the principle is this:
1.) Use ActiveRecord's power to your benefit. You've got the model association, so you don't need to do the lookup yourself. #user.bands should return an array of bands the user belongs to since they belong to those bands THROUGH bandmemberships.
2.) If you need to get to something, don't walk the tree over 2 or 3 items. e.g. #user.band.first.tour.first is bad bad juju. Create a method that finds this on the User model and then go from there, e.g.
def next_tour
User.tour.first etc etc
end
and then call it in the view as #user.next_tour or whatever.
3.) Use the power of render #collection_name and use the defaults to clean your code up. It's easier to read and better than a lot of partials floating around in shared/.
This is something I harp on a lot when I give my Rails View talks, the partials should belong in the folder for the controller they exist under. /app/views/tours/_tour.html.erb and so forth would be better than the tour_item under shared. It's the rendering of a single tour entry for anywhere in the app.
I'm not sure about tourstopmemberships as a join model either. What are you joining it to? Why not just have a tour has many stops and stops belong to a tour? If you're looking at a venue model as well, then perhaps stops is the join model between tours and venues. That then allows you to add additional meta data onto the stop
stop.tour
stop.venue
stop.start_time
stop.support_act (which could be a different relationship)
etc.
I have a one-to-many relationship in my rails application:
I have a User class that can have many Devices
I have a Device class that belongs to a User
My Models are designed like this:
class User < ActiveRecord::Base
has_many :devices
end
class Device < ActiveRecord::Base
belongs_to :user
end
Regarding views, when I want to display all Users and list their associated Devices I use this code:
<%= user.devices.each do |device| %>
<%= device.id %>
<% end %>
The output is: (only 1 device right now)
1 #<Device:0x101f45e50>
What I do not understand is why
#<Device:0x101f45e50>
is showing up after the id
replace equal sign
<% user.devices.each do |device| %>
<%= device.id %>
<% end %>
To give a litte more context so you know why this occurred, the = parses the output AND displays the result from the statement in the resulting HTML, where the - parses the line but does NOT display the result — since ruby passes a result at every new statement, you must put your = and - in the right spots.
Documentation is your friend (this is for HAML, but is still a good explanation)
I have a country model and a places model - a country has_many places, and a place belongs_to a country. A place model also has_many posts (which belong_to a place). I would like to aggregate all the posts from places that belong to a certain country into a feed - rather like a friend activity feed on a social networking site. I'm having a bit of trouble figuring out the appropriate search to do, any help would be much appreciated! At the moment I have:
country.rb
has_many :places
has_many :posts, :through => :places
def country_feed
Post.from_places_belonging_to(self)
end
post.rb
belongs_to :place
scope :from_places_belonging_to, lambda { |country| belonging_to(country) }
private
def self.belonging_to(country)
place_ids = %(SELECT place_id FROM places WHERE country_id = :country_id)
where("place_id IN (#{place_ids})", { :country_id => country })
end
end
Then in the country show.html.erb file:
<h3>Posts</h3>
<% #country.country_feed.each do |post| %>
<%= link_to "#{post.user.username}", profile_path(post.user_id) %> posted on <%=link_to "#{post.place.name}", place_path(post.place_id) %> <%= time_ago_in_words post.created_at %> ago:
<%= simple_format post.content %>
<% end %>
This runs without any errors, but gives a feed of all the posts, rather than selecting the posts for that particular country. What's the best way to get this working? I've got a sneaking suspicion that I might be over-complicating matters...thanks in advance!
It looks like there's a syntax error in the subquery. places_id does not exist in the places table, so it really ought to read:
SELECT id FROM places WHERE country_id = :country_id
You'd think the invalid subquery would raise an exception, but it doesn't -- or else it is handled silently. Very odd.
But unless I'm missing something, you could replace all this just by using:
<% #country.posts.each do |post| %>
No?