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?
Related
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 creating a has_and_belongs_to_many relationship with Rails. Each group has many participants and each participant can be part of many groups.
The relationship seems to be set up ok as I can use check boxes to add relationships using this in my form:
<%= collection_check_boxes(:group, :participant_ids, #participants, :id, :name) %>
However, I need to use a hidden field to submit these relationships (I use AJAX to fetch them in the view) with an array of ids (e.g. [1, 3]). I've tried using a text field like this but it doesn't save the data:
<%= f.text_field :participant_ids %>
When participant_ids saves using the checkboxes and I output it on the show view it's an array of ids but I can't seem to submit it in that format to start with.
Why can't I submit the participant_ids using a text/hidden field and is there a way around this?
For reference I've set up the join table and the models look like this:
class Group < ActiveRecord::Base
has_and_belongs_to_many :participants
end
class Participant < ActiveRecord::Base
has_and_belongs_to_many :groups
end
I also modified the group controller to work with strong parameters like this:
def group_params
params.require(:group).permit(:user_id, :purpose, :participant_ids => [])
end
I can post more code if necessary.
This answer worked for me. You will have to
<% #participants.each do |participant| %>
<% f.hidden_field 'participant_ids][', :value => participant.id %>
<% end %>
I thought I could beat this to death by systematically moving through options.
It Won.
index.html.erb and form.html.erb work as things are now.
I have a many to many relationship between Bids and Contacts, with bids_contacts in between with bid_id and customer_contact_id in that table.
Here's the contacts then bids models:
has_and_belongs_to_many :bid_customer_contacts, :class_name => 'Bid',
:association_foreign_key => 'bid_id'
has_and_belongs_to_many :customer_contacts, :class_name => 'Contact', :foreign_key => 'customer_contact_id'
Nothing special in bids_controller.rb
The show.html.erb file errors with:
Mysql2::Error: Unknown column 'bids_contacts.contact_id' in 'on clause':
SELECT `contacts`.* FROM `contacts` INNER JOIN `bids_contacts'
ON `contacts`.`id` = `bids_contacts`.`contact_id'
WHERE 'bids_contacts`.`customer_contact_id` = 15
The code is:
<b>Customer Contacts:</b>
<% if !#bid.customer_contacts.empty? %> <<===============
<ul>
<% #bid.contacts.each do |bc| %>
<li><%= link_to(bc.name, bc) %></li>
<% end %>
</ul>
<% else %>
No Customer Contacts<br/>
<% end %>
I've tried contact_id in the M2M table rather than customer_contact_id, but I just get different errors.
Let me know if I need to share something else to get this mystery solved.
Thanks.
In your view, you're calling
#bid.customer_contacts
and then later
#bid.contacts
You probably want
#bid.customer_contacts
both times, since you defined the method this way with
has_and_belongs_to_many :customer_contacts ...
I assume you have customer_contact_id as a field in your database, but when you call .contacts your view looks for the contact_id field which doesn't exist.
Could you tell me if there is a better way.
Models:
class Skill
has_many :tags
has_many :positions
end
class Tag
belongs_to :skill
has_and_belongs_to_many :positions
end
class Position
belongs_to :skill
has_and_belongs_to_many :tags
end
I want to list all skills and the tags of their positions. Like this:
skill - tag tag tag tag
skill - tag tag
...
I managed to acheive it like this:
<% #skills.each do |skill| %>
<%= skill.name %>
<% skill.positions.collect{|p| p.tags}.flatten.uniq.each do |t| %>
<%= t.name %>
<% end %>
<% end %>
And my skills_controller:
def index
#skills = Skill.all
end
Is this the right way?
Since tagging is a pretty common problem I'd recommend taking a look at acts-as-taggable-on, a widely used and very good gem for adding tags to any Rails model.
Regardless, your models look pretty good (except that has_and_belongs_to_many is often eschewed in favor for has_many :through), but I do see room for improvement here:
<% skill.positions.collect{|p| p.tags}.flatten.uniq.each do |t| %>
<%= t.name %>
<% end %>
Firstly, this is a lot of business logic to put in your view. You should do this in your controller instead. Secondly, it would be more performant to do it the other way around:
#tags = Tag.all :conditions => [ "tag.id IN (?)", skill.positions.map &:id ]
There are more efficient ways still to do this, but this ought to give you an idea.
First off - with any performance question you should measure the performance. Create twice as many skills, tags, and positions as you expect to need in a rake task. Then measure the page load times. If they're OK for your needs, cool. Otherwise, keep reading.
This is not particularly efficient since you will go across the network to the database for every skill to position mapping, and then again for every position to tag mapping. You can use the includes method to load using fewer queries - see the Rails guide on querying.
To simplify things, I have 3 tables :
Person
has_many :abilities, through => :stats
Ability
has_many :people, through => :stats
Stats
belongs_to :people
belongs_to :abilities
Stats has an extra attribute called 'rating'.
What I'd like to do is make an edit person form that always lists all the abilities currently in the database, and lets me assign each one a rating.
For the life of me, I can't figure out how to do this. I managed to get it to work when creating a new user with something like this:
(from the people controller)
def new
#character = Character.new
#abilities = Ability.all
#abilities.each do |ability|
#person.stats.build(:ability_id => ability.id )
end
end
From the people form:
<% for #ability in #abilities do %>
<%= fields_for "person[stats_attributes]" do |t| %>
<div class="field">
<%= t.label #ability.name %>
<%= t.hidden_field :ability_id, :value => #ability.id, :index => nil %>
<%= t.text_field :rating, :index => nil %>
</div>
<% end %>
<% end %>
This successfully gives me a list of abilities with ratings boxes next to them, and lets me save them if i'm making a new user.
The problem is that if I then load up the edit form (using the same form partial), it doesn't bring back the ratings, and if I save, even with the exact same ratings, it creates duplicate entries in the stats table, instead of updating it.
I realize I'm a terrible programmer and I'm probably doing this the wrong way, but how do I get the edit form to recall the current ratings assigned to each ability for that user, and secondly how do i get it to update the rating instead of duplicating it if the combination of person and ability already exists?
Shouldn't that be
Character
has_many :stats
has_many :abilities, through => :stats
Ability
has_many :stats
has_many :characters, through => :stats
Stat
belongs_to :character
belongs_to :ability
?
Also, is it Person or Character? You refer variously to both. (I'm going to go with Character in my answer)
I think you've fallen foul of the "I'll try to make a simplified version of my schema in order to attempt to illustrate a problem but instead make things more complex and muddle the issue by screwing it up so it doesn't make sense" syndrome. Anyway, there's a couple of issues i can see:
1) first thing is that you're adding all the possible abilities to a character as soon as they're created. This is silly - they should start out with no abilities by default and then you create join table records (stats) for the ones they do have (by ticking checkboxes in the form).
2) A simple way to manipulate join records like this is to leverage the "ability_ids=" method that the has_many :abilities macro gives you - referred to as "collection_ids=" in the api http://railsbrain.com/api/rails-2.3.2/doc/index.html?a=M001885&name=has_many
In other words, if you say
#character.ability_ids = [1,12,30]
then that will make joins between that character and abilities 1, 12 and 30 and delete any other joins between that character and abilities not in the above list. Combine this with the fact that form field names ending in [] put their values into an array, and you can do the following:
#controller
def new
#character = Character.new
#abilities = Ability.all
end
#form
<% #abilities.each do |ability| %>
<div class="field">
<%= t.label #ability.name %>
<%= check_box_tag "character[ability_ids][]" %>
</div>
<% end %>
#subsequent controller (create action)
#character = Character.new(params[:character]) #totally standard code
Notice that there's no mention of stats here at all. We specify the associations we want between characters and abilities and let rails handle the joins.
Railscasts episodes 196 and 197 show how to edit several models in one form. Example shown there looks similar to what you're trying to do so it might help you out (same episodes on ascicasts: 196, 197).