Rails adding resource id to another resource via HABTM - ruby-on-rails-3

I have 3 pertinent models:
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
end
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :galleries
end
class Gallery < ActiveRecord::Base
belongs_to :group
end
I want to be able to create users and galleries within a group so that only users who are members of the group can view the galleries that belong to that group. I also want users to be able to view galleries of other groups they belong to (hence the HABTM association).
I'm having difficulty in conceptualizing how this works with controllers, and perhaps I'm over thinking the problem. If I create a Group, and then I go to create a user, what is the best way to go about adding the current group_id to the user model? Same thing goes for the gallery model...
Does that make sense?
Let me know if I need to clarify or add code samples.
Thank you very much for your help.
EDIT: Clarification
I definitely didn't make any sense in my initial question, but I did manage to find the answer, with help from a friend.
What I ended up doing is passing the group_id to the form via the params hash like so:
<%= link_to "Add User", new_admin_user_path(:group_id => #group.id) %>
<%= link_to "Add Gallery", new_gallery_path(:group_id => #group.id) %>
Then using a hidden field in my form, assigning the group_id to the "group_id" hidden field:
<%= hidden_field_tag :group_id, params[:group_id] %>
And, finally, in my create methods, adding these lines before the save assigns the group_id perfectly:
# Gallery only has one group
#gallery.group_id = params[:group_id]
# Users can belong to many groups
#user.groups << Group.find(params[:group_id])
I'll still need to sit down and wrap my head around the answers you both provided. Thank you very much for taking the time to help me out. I really appreciate it.

When you are using find method from your controller you can make it like this:
Gallery.find :all, :joins => "INNER JOIN groups ON groups.gallery_id = galleries.id INNER JOIN users ON users.group_id = groups.id", :conditions => "users.id = #{#your_current_user_id}"
It must find all galleries of groups which the user belongs.

I would not define this in the controller as Sebes suggests, but rather in the User model.
Adapting his idea:
def galleries
Gallery.joins(:groups => :users).where("users.id = ?", self.id)
end
Then to get a collection of the galleries for the current_user object:
current_user.galleries

Related

Simple has_many :through association

Pretty simple setup. I want to make sure my understanding of the ORM is correct.
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, through => memberships
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, through => memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
Now when a user creates a group I want the membership record in the link table to get populated. It should be an atomic(transaction).
class GroupsController < ApplicationController
def create
#group = current_user.groups.build(params[:group])
if #group.save
flash[:notice] = "Group has been created."
redirect_to #group
else
flash[:alert] = "Group has not been created."
render :action => "new"
end
end
end
This doesn't work. The group gets saved but no membership record created in the link table. However using a create vs build works. Is that how it's supposed to work?
What's the best approach here?
This behaviour is by design. As you mentioned, you can either do #group = current_user.groups.create(params[:group]).
Or you can add an additional statement to create a record in the join model's table as :
#group = current_user.groups.build(params[:group])
if #group.save
#group.memberships.create(:user_id => current_user)
# redirect and notify
Well, the reason being simply building #group and saving it does not add an additional record in the join table.
Infact, in this case, #group = current_user.groups.build(params[:group]) is somewhat similar to #group = Group.new(params[:group]). The difference being, in the former case, current_user.groups will contain #group (you can try that in Groups#create before redirect) but doing current_user.reload followed by current_user.groups will yield [].
The best way to do this is somewhat similar to your approach. Have a simple create action as :
def create
#group = Group.new(params[:group])
# if else for save and redirect
However, for this to work the params hash submitted to Groups#create should include user_ids as :
"group"=>{"name"=>"new group", "user_ids"=>["1", "2", "3"]}, "commit"=>"Create Group"
May be that was the reason why #bruno077 was asking you to paste your view's code, so as to get an idea on user_ids params being passed.
So, if the new group form contains fields to select multiple users, then its simple create action as shown right above (because of the user_ids params). But if have a new group form with no options to select users, then you are better off using the first option (one using create).

assign child to one of parents in rails form

I have problem how to create form for assigning child to one of parents. My child-nodes are defined in system, i go to child (alias) and want to assign it to one of parents contacts) (or create new). How do I create such form?
class Alias < ActiveRecord::Base
belongs_to :user
belongs_to :contact
end
class Contact < ActiveRecord::Base
has_many :aliases
belongs_to :user
end
So i have couple objects of Alias and couple of Contact, but I don't want to go to Contact and there assign Alias, but go to Alias, and pickup one of Contacts from (example) select box.
There are many ways to select the association id. A select box is probably the most space efficient on a page, but sometimes radio buttons work nice if there are few possible associations.
I think something like this might suit you. This assumes you assign #contacts to something like Contact.all inside your controller.
= form_for #alias do |f|
= f.label :contact_id, "Contact"
= f.collection_select :contact_id, #contacts, :id, :full_names
This assumes you have a field called "full_names" in your contact. Put whatever field you want to show in the select box.
Creating the Contact from the Alias is more tricky, and personally I wouldn't recommend it. Here are a couple questions which explain how to do it though:
Does accepts_nested_attributes_for work with belongs_to?
Getting fields_for and accepts_nested_attributes_for to work with a belongs_to relationship
I hope that helps.

Rails 3.1 associations and counting?

I have two models in my Rails application, Users and Calls.
The model associations are set as follows:
user model
has_many :calls
call model
belongs_to :user
I am trying to call, within the application.html.erb layout, the number of calls that the current user has.
Currently, I am using the following string:
<%= Call.count %>
Which works but it's obviously counting all calls, not just the calls that the current user has.
So I swapped that for the following:
<%= current_user.Call.calls.count %>
I am confused as to how to do this. I need to be able to call the count from anywhere so I can then start working on counting based on the last 30 days etc.
Do this:
current_user.calls.count
You can do the same thing with any User object:
user = User.find(1)
user.calls.count
You then can chain more conditions to do the date-based counts:
user.calls.where("calls.created_at > ?", 30.days.ago).count
You don't really want to be putting database calls within views, you really want to put that into a controller. You'd be looking for something like User.find(current_user.id).calls.count as this will then use the association of which you've set up, or if you want to disregard the model relation you could do Call.where(:user_id => current_user.id).count
So put the below into the relevant controller (in the correct action), and likewise for the view.
Controller
#count = Call.where(:user_id => current_user.id).count
View
<%= #count %>

Modeling inheritance with Ruby/Rails ORMs

I'm trying to model this inheritance for a simple blog system
Blog has many Entries, but they may be different in their nature. I don't want to model the Blog table, my concern is about the entries:
simplest entry is an Article that has title and text
Quote, however, does not have a title and has short text
Media has a url and a comment...
etc...
What is a proper way to model this with Ruby on Rails? That is
Should I use ActiverRecord for this or switch to DataMapper?
I would like to avoid the "one big table" approach with lots of empty cells
When I split the data into Entry + PostData, QuoteData etc can I have belongs_to :entry in these Datas without having has_one ??? in the Entry class? That would be standard way to do it in sql and entry.post_data may be resolved by the entry_id in the postdata table.
EDIT: I don't want to model the Blog table, I can do that, my concern is about the entries and how would the inheritance be mapped to the table(s).
I've come across this data problem several times and have tried a few different strategies. I think the one I'm a biggest fan of, is the STI approach as mentioned by cicloon. Make sure you have a type column on your entry table.
class Blog < ActiveRecord::Base
# this is your generic association that would return all types of entries
has_many :entries
# you can also add other associations specific to each type.
# through STI, rails is aware that a media_entry is in fact an Entry
# and will do most of the work for you. These will automatically do what cicloon.
# did manually via his methods.
has_many :articles
has_many :quotes
has_many :media
end
class Entry < ActiveRecord::Base
end
class Article < Entry
has_one :article_data
end
class Quote < Entry
has_one :quote_data
end
class Media < Entry
has_one :media_data
end
class ArticleData < ActiveRecord::Base
belongs_to :article # smart enough to know this is actually an entry
end
class QuoteData < ActiveRecord::Base
belongs_to :quote
end
class MediaData < ActiveRecord::Base
belongs_to :media
end
The thing I like about this approach, is you can keep the generic Entry data in the entry model. Abstract out any of the sub-entry type data into their own data tables, and have a has_one association to them, resulting in no extra columns on your entries table. It also works very well for when you're doing your views:
app/views/articles/_article.html.erb
app/views/quotes/_quote.html.erb
app/views/media/_media.html.erb # may be medium here....
and from your views you can do either:
<%= render #blog.entries %> <!-- this will automatically render the appropriate view partial -->
or have more control:
<%= render #blog.quotes %>
<%= render #blog.articles %>
You can find a pretty generic way of generating forms as well, I usually render the generic entry fields in an entries/_form.html.erb partial. Inside that partial, I also have a
<%= form_for #entry do |f| %>
<%= render :partial => "#{f.object.class.name.tableize}/#{f.object.class.name.underscore}_form", :object => f %>
<% end %>
type render for the sub form data. The sub forms in turn can use accepts_nested_attributes_for + fields_for to get the data passed through properly.
The only pain I have with this approach, is how to handle the controllers and route helpers. Since each entry is of its own type, you'll either have to create custom controllers / routes for each type (you may want this...) or make a generic one. If you take the generic approach, two things to remember.
1) You can't set a :type field through update attributes, your controller will have to instantiate the appropriate Article.new to save it (you may use a factory here).
2) You'll have to use the becomes() method (#article.becomes(Entry)) to work with the entry as an Entry and not a subclass.
Hope this helps.
Warning, I've actually used Media as a model name in the past. In my case it resulted in a table called medias in rails 2.3.x however in rails 3, it wanted my model to be named Medium and my table media. You may have to add a custom Inflection on this naming, though I'm not sure.
You can handle this easily using ActiveRecord STI. It requires you to have a type field in your Entries table. This way you can define your models like this:
def Blog > ActiveRecord::Base
has_many :entries
def articles
entries.where('Type =', 'Article')
end
def quotes
entries.where('Type =', 'Quote')
end
def medias
entries.where('Type =', 'Media')
end
end
def Entry > ActiveRecord::Base
belongs_to :blog
end
def Article > Entry
end
def Quote > Entry
end
def Media > Entry
end

Creating a news feed in Rails 3

I want to create an activity feed from recent article and comments in my rails app. They are two different types of activerecord (their table structures are different).
Ideally I would be able to create a mixed array of articles and comments and then show them in reverse chronological order.
So, I can figure out how to get an array of both articles and comments and then merge them together and sort by created_at, but I'm pretty sure that won't work as soon as I start using pagination as well.
Is there any way to create a scope like thing that will create a mixed array?
One of the other problems for me, is that it could be all articles and it could be all comments or some combination in between. So I can't just say I'll take the 15 last articles and the 15 last comments.
Any ideas on how to solve this?
When I've done this before I've managed it by having a denormalised UserActivity model or similar with a belongs_to polymorphic association to an ActivitySource - which can be any of the types of content that you want to display (posts, comments, up votes, likes, whatever...).
Then when any of the entities to be displayed are created, you have an Observer that fires and creates a row in the UserActivity table with a link to the record.
Then to display the list, you just query on UserActivity ordering by created_at descending, and then navigate through the polymorphic activity_source association to get the content data. You'll then need some smarts in your view templates to render comments and posts and whatever else differently though.
E.g. something like...
user_activity.rb:
class UserActivity < ActiveRecord::Base
belongs_to :activity_source, :polymorphic => true
# awesomeness continues here...
end
comment.rb (post/whatever)
class Comment < ActiveRecord::Base
# comment awesomeness here...
end
activity_source_observer.rb
class ActivitySourceObserver < ActiveRecord::Observer
observe :comment, :post
def after_create(activity_source)
UserActivity.create!(
:user => activity_source.user,
:activity_source_id => activity_source.id,
:activity_source_type => activity_source.class.to_s,
:created_at => activity_source.created_at,
:updated_at => activity_source.updated_at)
end
def before_destroy(activity_source)
UserActivity.destroy_all(:activity_source_id => activity_source.id)
end
end
Take a look at this railscast.
Then you can paginate 15 articles and in app/views/articles/index you can do something like this:
- #articles.each do |article|
%tr
%td= article.body
%tr
%td= nested_comments article.comment.descendants.arrange(:order => :created_at, :limit => 15)
This assumes the following relations:
#app/models/article.rb
has_one :comment # dummy root comment
#app/models/comment.rb
belongs_to :article
has_ancestry
And you add comments to an article as follows:
root_comment = #article.build_comment
root_comment.save
new_comment = root_comment.children.new
# add reply to new_comment
new_reply = new_comment.children.new
And so forth.