A user has_many :donations, a project has_many :donations, and a donation belongs_to :user and belongs_to :project.
I'm looking for a sensible way to extract the projects associated with a user (through donations) into an array.
I'm currently doing:
def index
#user = User.find params[:user_id]
#projects = []
#user.donations.each do |donation|
#projects << donation.project
end
end
I feel like I'm missing something obvious, as this seems lame. Is there a better way to do this?
Edit
I accidentally simplified this too far. A user can also be associated with a project through other models, so #projects = #user.projects isn't going to do what I need it to.
class User < AR::Base
has_many :donations
has_many :projects, :through => :donations
…
end
#user.projects
should work.
For gathering many association collections see my previous answer. You will need to adapt it to use the through associations (just treat them as normal has_masnys), but the same applies.
Related
My main models are that I have users and I have recipes.
I'm trying to implement a tagging structure such that each user can tag a recipe with individual tags. So when viewing a recipe, they would only see tags that they themselves have added.
I created two models hashtags, and hashtagging that is the join table. It is set up as so:
models/hashtags.rb
class Hashtag < ActiveRecord::Base
has_many :hashtaggings
has_many :recipes, through: :hashtaggings
has_many :users, through: :hashtaggings
end
models/hashtagging.rb
class Hashtagging < ActiveRecord::Base
belongs_to :user
belongs_to :hashtag
belongs_to :recipe
end
models/recipe.rb
class Recipe < ActiveRecord::Base
...
has_and_belongs_to_many :users
has_many :hashtaggings
has_many :hashtags, through: :hashtaggings
....
def all_hashtags=(name)
self.hashtags = name.split(",").map do |name|
Hashtag.where(name: name.strip).first_or_create!
end
end
def all_hashtags
self.hashtags.map(&:name).join(",")
end
end
class User < ActiveRecord::Base
...
has_many :hashtaggings
has_many :hashtags, through: :hashtaggings
...
end
This works great for creating the hash tags however I'm at a loss for how to incorporate the user aspect of it. How when assigning the tags can I also assign the current user to those tags and then just return those?
There are two steps, creation and display...
Creation
This one is going to be tricky, because you can't simply do something like...
#recipe.hashtags.create(name: "Tasty as heck!")
...because neither the recipe, nor the hashtag, knows anything about the user. It's a two-step process.
class RecipeHashtagsController
def create
current_hashtag = Hashtag.find_or_create_by(name: "Scrumptious!")
current_recipe = Recipe.find(params[:recipe_id])
hashtagging = Hashtagging.find_or_create_by(hashtag: current_hashtag, user: current_user, recipe: current_recipe)
# redirect_to somewhere_else...
end
end
A few things I did there:
I'm using find_or_create_by since I'm assuming you don't want either duplicate hashtags or duplicate hashtaggings. You could also just create.
I'm assuming you have some kind of current_user method in ApplicationController or through a gem like Devise.
I'm assuming you have a controller like RecipeHashtags, and a nested resource that matches and will provide an id from the route. I recommend nesting here since you aren't simply creating a hashtag, but you are creating a hashtag within the specific context of a recipe.
Displaying
This gets tricky, because you want to display recipe.hashtags but with a condition on the join table hashtaggings. This is not super straightforward.
What I'm thinking is you might want to be able to do something like...
#recipe.hashtags_for_user(current_user)
...which could be in the form of a method on Recipe.
class Recipe
def hashtags_for_user(user)
Hashtags.joins(:hashtaggings).where(hashtaggings: { user_id: user.id, recipe_id: self.id })
end
end
You can read more about the hash inside the .where call in the Active Record Querying Rails Guide (check out section 12.3).
Edit: The Controller
I recommend creating RecipeHashtags as a nested route pointing to separate controller, since the creation of a hashtag is dependent on which recipe it's being created for.
routes.rb
resources :recipes do
resources :hashtags, only: [:create]
end
...which will show something like the following when you do rake routes in the terminal...
POST /recipes/:recipe_id/hashtags(.:format) recipe_hashtags#create
Note: I'm assuming you have a resource for resource for recipes. If you don't, this may duplicate some routes and have other, unintended results.
The default behavior for Rails is to assume you've got a controller like recipe_hashtags_controller based on how you defined your resources. You can always override this if you like.
i need help with the following topic:
I'm trying to create an array of users based on their ids
def show
#appointment = Appointment.where(:event_id => :id).all
#users = Array.new
#appointment.each do |appointment|
#users.fill(User.find(appointment.user_id))
end
end
First, im getting all appointments which event_id are the same to :id (which comes from the Event table) . Then i proceed to create an array to be filled later with users inside the .each do expression.
The problem is that #users is empty after the expresion ends.
What am i doing wrong?
Much simpler:
#users = User.find(Appointment.where(event_id: <id>).pluck(:id))
Your code does not work as you misunderstood what method fill does - it substitutes all elements of the array with passed object (pretty much like this, might take some extra params to alter its behaviour a little bit). You were most liekly looking for push or unshift methods.
Much better solution
If I am correct, your associations most likely looks like this:
Event has_many :appointments
Appointment belongs_to :user
n that case, you can simply create has_many :through association:
class Event < ActiveRecord::Base
has_many :appointments
has_many :users, through: :appointments
end
Then your query is just:
Event.find(<id>).users
#users = Appointment.includes(:user).where(:event_id => <id>).map(&:user)
should work, assuming you have the User has_many :appointments & Appointments belongs_to :user associations setup.
In the project management app I'm working on, I'm currently working on a page for managing tickets, which I want should contain of the following:
- The tickets that the user has created
- The tickets that belongs to projects that the user has created
The tricky part is to use the right code in the controller, which is where I need help.
'#users_tickets'works fine, as well as '#owned_projects'. However, the last thing which is creating an array that contains of the tickets that belongs projects that the user owns, is something I need help me (yes, I understand that my poor try with an each loop is totally the wrong way to go here).
How can I achieve what I want?
Tickets controller:
1. def manage
2. #users_tickets = Ticket.where(:user_id => current_user.id)
3. #owned_projects = Project.where(:user_id => current_user)
4.
5. #owned_projects.each do |project|
6. #tickets_for_owned_projects = Ticket.where(:project_id => project.id)
7. end
8. end
Tables:
tickets table:
project_id
ticket_status_id
user_id
title
description
start_date
end_date
projects table:
user_id
title
description
start_date
end_date
If you're using a has_many association, then it should as simple as
class User < ActiveRecord::Base
has_many :projects
has_many :tickets
has_many :project_tickets, through: :projects, class_name: 'Ticket', source: :tickets
#...
end
class Project < ActiveRecord::Base
has_many :tickets
#...
end
# in tickets controller
def manage
#tickets = current_user.tickets
#tickets_for_owned_projects = current_user.project_tickets
end
UPD: The approach above should work. I'm literally falling asleep right now and can't define what is wrong here. Would really appreciate if someone looked into it.
Here's another way around though.
class User < ActiveRecord::Base
has_many :projects
has_many :tickets
def project_tickets
result = []
self.projects.each do |project|
result << project.tickets
end
result.flatten
end
#...
end
I have a class that looks something like this:
class User < ActiveRecord:Base
has_many :users_companies
has_many :companies, :through => :users_companies
end
For plain users, I'd like user.companies to refer to the standard association method, but when a user is an admin, I want User.all (i.e., admins have access to all companies). The simplest way I can think of to implement this (and what I've always done in the past) is use a scope on the Company class, such as:
scope :accessible_by, lambda { |user| ... }
The only problem is that this just doesn't feel right. Instead of writing a controller action that includes:
#companies = Company.accessible_by(current_user)
I'd feel more comfortable writing
#companies = current_user.companies
Is there a good way to override the User#companies method to accommodate this kind of behavior? Or, should I be happy with using a scope on Company?
I'm wrestling with a similar problem. The only acceptable solution I can devise is an association extension, which overrides the query for admin users and passes normal users' queries, unmolested.
# this works for me in rails 3.1
class User < ActiveRecord:Base
has_many :users_companies
has_many :companies, :through => :users_companies do
def visible
if proxy_association.owner.admin?
UsersCompany.scoped
else
self
end
end
end
end
User.where(:admin => true).first.companies.visible == UsersCompany.all
I'm fairly new to Rails, but this is an interesting question so I figured I'd toss in my two cents. It seems that you should be able to extend your association in User with a companies method that checks self.is_admin? (or similar) and returns what you need. See http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many#461-User-a-block-to-extend-your-associations
Nice question. I was wondering if something like the following is an option you would consider
class User < ActiveRecord:Base
has_many :users_companies
has_many :companies, :through => :users_companies
def viewable_companies
admin? ? Company.all : self.companies
end
end
I know the naming is horrible but, you know, naming things is serious stuff :)
I have two models with a many to many relationship using has_and_belongs_to_many. Like so:
class Competition < ActiveRecord::Base
has_and_belongs_to_many :teams
accepts_nested_attributes_for :teams
end
class Team < ActiveRecord::Base
has_and_belongs_to_many :competitions
accepts_nested_attributes_for :competitions
end
If we assume that I have already created several Competitions in the database, when I create a new Team, I would like to use a nested form to associate the new Team with any relevant Competitions.
It's at this point onwards that I really do need help (have been stuck on this for hours!) and I think my existing code has already gone about this the wrong way, but I'll show it just in case:
class TeamsController < ApplicationController
def new
#team = Team.new
#competitions.all
#competitions.size.times {#team.competitions.build}
end
def create
#team = Team.new params[:team]
if #team.save
# .. usual if logic on save
end
end
end
And the view... this is where I'm really stuck so I won't both posting my efforts so far. What I'd like it a list of checkboxes for each competition so that the user can just select which Competitions are appropriate, and leave unchecked those that aren't.
I'm really stuck with this one so appreciate any pointing in the right direction you can provide :)
The has_and_belongs_to_many method of joining models together is deprecated in favor of the new has_many ... :through approach. It is very difficult to manage the data stored in a has_and_belongs_to_many relationship, as there are no default methods provided by Rails, but the :through method is a first-class model and can be manipulated as such.
As it relates to your problem, you may want to solve it like this:
class Competition < ActiveRecord::Base
has_many :participating_teams
has_many :teams,
:through => :participating_teams,
:source => :team
end
class Team < ActiveRecord::Base
has_many :participating_teams
has_many :competitions,
:through => :participating_teams,
:source => :competition
end
class ParticipatingTeam < ActiveRecord::Base
belongs_to :competition
belongs_to :team
end
When it comes to creating the teams themselves, you should structure your form so that one of the parameters you receive is sent as an array. Typically this is done by specifying all the check-box fields to be the same name, such as 'competitions[]' and then set the value for each check-box to be the ID of the competition. Then the controller would look something like this:
class TeamsController < ApplicationController
before_filter :build_team, :only => [ :new, :create ]
def new
#competitions = Competitions.all
end
def create
#team.save!
# .. usual if logic on save
rescue ActiveRecord::RecordInvalid
new
render(:action => 'new')
end
protected
def build_team
# Set default empty hash if this is a new call, or a create call
# with missing params.
params[:team] ||= { }
# NOTE: HashWithIndifferentAccess requires keys to be deleted by String
# name not Symbol.
competition_ids = params[:team].delete('competitions')
#team = Team.new(params[:team])
#team.competitions = Competition.find_all_by_id(competition_ids)
end
end
Setting the status of checked or unchecked for each element in your check-box listing is done by something like:
checked = #team.competitions.include?(competition)
Where 'competition' is the one being iterated over.
You can easily add and remove items from your competitions listing, or simply re-assign the whole list and Rails will figure out the new relationships based on it. Your update method would not look that different from the new method, except that you'd be using update_attributes instead of new.