rails activerecord validation with associations - ruby-on-rails-3

I have model associations as follows:
class Group < ActiveRecord::Base
has_many :group_links, :dependent => :destroy
end
class GroupLink < ActiveRecord::Base
belongs_to :group
validates_presence_of :group_id
validates_presence_of :url, :message => "We need a url to create a link"
validates_uniqueness_of :url, :message => "A link with this url already exists"
validates_presence_of :text, :message => "We need a text to create a link"
validates_uniqueness_of :text, :message => "A link with this text already exists"
end
I want it to work like in each group the group links should be unique. However the way it works is, it throws the validations errors even if some other group has thins group link.
what am i doing wrong?
thanks in advance,

I used :scope => :group_id for my uniqueness validations to get this to work

Related

cant setup an active admin resource form with has many :through assoc

I'm working on a rails (3.7.8) app and using active admin to manage resources for the ff models:
class AdminUser < ActiveRecord::Base
has_many :user_article_categories, :include => :article_categories
has_many :article_categories, :through => :user_article_categories,
:source => :admin_user
has_many :articles, :through => :user_article_categories,
:source => :admin_user
# ...
end
class UserArticleCategory < ActiveRecord::Base
belongs_to :admin_user
belongs_to :article_category
attr_accessible :admin_user_id, :article_category_id, :included
attr_accessor :included
after_find :set_included
private
def set_included
self.included = "1"
end
# ...
end
the "included" attribute was based on a solution presented here
class ArticleCategory < ActiveRecord::Base
has_many :user_article_categories, :include => :admin_users
has_many :admin_users, :through => :user_article_categories,
:source => :article_category
has_many :articles, :through => :user_article_categories,
:source => :article_category
# ...
end
but I seem not to get setting up (correctly) a form for admin_users, such that creating a new admin_user would have all article_categories displayed as a list of checkboxes
while a persisted admin_user for update would have all article_categories checkboxes displayed but wit all previously set article-categories checked, so that an update would remove unchecked checkboxes and add newly checked ones to what goes to the join-table
for admin/admin_users.rb I create the form as follows, this does not work, though it renders correctly, any help will be appreciated
form do |f|
if f.object.persisted? and current_admin_user.id == f.object.id
f.inputs "Admin Details" do
f.input :email
f.inputs :for => user_article_categories do |usr_art_catr|
usr_art_catr.input :article_category_id, :hidden
usr_art_catr.input :included
end
end
else
f.inputs "Admin Details" do
f.input :email
f.input :superuser, :label => "Super User Priveleges"
f.input :article_categories, :as => :check_boxes,
:collection => ArticleCategory.select("id, name")
end
end
f.buttons
end
Actually, to display a list of checkboxes of all article_categories and check all already checked article categories for a given admin_user on update.
Formtastic, when rendering the show form for the form's object, calls a method provided on the form object via
f.input :method_to_be_called, :as => :checkboxes
which formtastic would compare its result with a collection provided via
the
:collection => any_valid_ruby_object
but both should return the same kinds; array/array or hash/hash, whatever, to determine which checkboxes should be checked, by performing a difference on the two collections.
The method called by formtastic could be an instance method on admin_user that queries the join-table, to determine which checkboxes should be checked and builds an array of that from the related article_categories table or returns an empty array when there is none.
This allows formtastic do what is right, as least in this context. This solution makes the "included" attribute on user_article_categories (the join-table) redundant!

Rails: validation fail for a nested model on key field presence check

I have a model User and a nested model Mobility
class User < ActiveRecord::Base
has_many :mobilities, :dependent => :destroy
accepts_nested_attributes_for :mobilities
end
and
class Mobility < ActiveRecord::Base
belongs_to :mobile_user, :class_name => 'User'
validates :city_id, :presence =>true
validates :user_id, :presence =>true
validates :city_id, :uniqueness => {:scope => [:user_id]}
end
my view
=form_for #user, :as => :user, :html =>{ :class => 'form-horizontal'} do |f|
=f.fields_for :mobilities do |city_form|
=city_form.text_field :city_id, :id => "city_id_#{index}"
= f.submit "Retour"
my problem is that when I submit the form Rails render me this validation error:
Mobilities user > doit ĂȘtre rempli(e)
But if a I comment this line:
#validates :user_id, :presence =>true
Both, my Mobility and User objects get saved and know what: user_id field of #mobility is OK (indicatie my #user's ID)
If I send the form with 2 identical mobility inside, both model get saved but it seems my validation of uniqueness didn't check nothing because i have 2 Mobility object with same user_id and city_id in my database...
In fact it seems like my validation can't read my user_id 's key when validating.
I understand that because my User model did'nt get saved yet and doesnt have any ID yet... but that is my question:
How can i check both: presence of user_id and uniqueness with scope ???

Rails 3 current user id to comments?

So Im working on a rails app where users can comment on photos or videos another user has uploaded and so far everything is great except I am not able to get the current user_id associated with the person who has commented on the post. This is what I have so far.
user.rb
has_many :comments, :dependent => :destroy
photo.rb
has_many :comments, :as => :commentable
video.rb
has_many :comments, :as => :commentable
comments_controller.rb
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
if #comment.save
redirect_to :id => nil, :notice => "Successfully created comment."
else
render :action => 'new'
end
end
How can I get the user id to appear with the current comments? I have the comment type and comment id I am just looking for a way to have it so the user_id can appear. Any suggestions?
You should add a hidden_field to your form partial where you store the current_user.id
something like:
<%= f.hidden_field :user_id, :value => current_user.id %>
of course you should have a field user_id in your comment model, as a comment belongs_to user and a user has_many comments.
update:
what ofca pointed out, this can approach can lead to security issues as the hidden field could be modified by the user in the browser, e.g. using firebug.
In this case it is probably better to to leave out this field in the view and create the comment in the controller by using
<%= current_user.comments.create(params[:comment]) %>
The way you have it now, it is only set up one way.
Plus you have to make it polymorphic
try adding:
comment.rb
belongs_to :user
belongs_to :commentable, :polymorphic => true

Setting up a polymorphic has_many :through relationship

rails g model Article name:string
rails g model Category name:string
rails g model Tag name:string taggable_id:integer taggable_type:string category_id:integer
I have created my models as shown in the preceding code. Articles will be one of many models which can have tags. The category model will contain all categories which may be assigned. The tag model will be a polymorphic join-table which represents tagged relationships.
class Article < ActiveRecord::Base
has_many :tags, :as => :taggable
has_many :categories, :through => :taggable
end
class Category < ActiveRecord::Base
has_many :tags, :as => :taggable
has_many :articles, :through => :taggable
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
I can't seem to get this to work, I can do it non polymorphic, but I must have something wrong with the polymorphic part. Any ideas?
Edit: Still not getting this right:
class Article < ActiveRecord::Base
has_many :taggables, :as => :tag
has_many :categories, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Category < ActiveRecord::Base
has_many :taggables, :as => :tag
has_many :articles, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
To create a polymorphic has_many :through, you must first create your models. We will use'Article,' 'Category,' and 'Tag' where 'Tag' is the join-model and Article is one of many objects which can be "tagged" with a category.
First you create your 'Article' and 'Category' models. These are basic models which do not need any special attention, just yet:
rails g model Article name:string
rails g model Category name:string
Now, we will create our polymorphic join-table:
rails g model Tag taggable_id:integer taggable_type:string category_id:integer
The join-table joins together two tables, or in our case one table to many others via polymorphic behavior. It does this by storing the ID from two separate tables. This creates a link. Our 'Category' table will always be a 'Category' so we include 'category_id.' The tables it links to vary, so we add an item 'taggable_id' which holds the id of any taggable item. Then, we use 'taggable_type' to complete the link allowing the link to know what it is linked to, such as an article.
Now, we need to set up our models:
class Article < ActiveRecord::Base
has_many :tags, :as => :taggable, :dependent => :destroy
has_many :categories, :through => :tags
end
class Category < ActiveRecord::Base
has_many :tags, :dependent => :destroy
has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
After this, setup your database using:
rake db:migrate
That's it! Now, you can setup your database with real data:
Category.create :name => "Food"
Article.create :name => "Picking the right restaurant."
Article.create :name => "The perfect cherry pie!"
Article.create :name => "Foods to avoid when in a hurry!"
Category.create :name => "Kitchen"
Article.create :name => "The buyers guide to great refrigeration units."
Article.create :name => "The best stove for your money."
Category.create :name => "Beverages"
Article.create :name => "How to: Make your own soda."
Article.create :name => "How to: Fermenting fruit."
Now you have a few categories and various articles. They are not categorized using tags, however. So, we will need to do that:
a = Tag.new
a.taggable = Article.find_by_name("Picking the right restaurant.")
a.category = Category.find_by_name("Food")
a.save
You could then repeat this for each, this will link your categories and articles. After doing this you will be able to access each article's categories and each categorie's articles:
Article.first.categories
Category.first.articles
Notes:
1)Whenever you want to delete an item that is linked by a link-model make sure to use "destroy." When you destroy a linked object, it will also destroy the link. This ensures that there are no bad or dead links. This is why we use ':dependent => :destroy'
2)When setting up our 'Article' model, which is one our 'taggable' models, it must be linked using :as. Since in the preceeding example we used 'taggable_type' and 'taggable_id' we use :as => :taggable. This helps rails know how to store the values in the database.
3)When linking categories to articles, we use:
has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
This tells the category model that it should have many :articles through :tags. The source is :taggable, for the same reason as above. The source-type is "Article" because a model will automatically set taggable_type to its own name.
You simply cannot make the join table polymorphic, at least Rails does not support this out of the box. The solution is (taken from Obie's Rails 3 way):
If you really need it, has_many :through is possible with polymorphic associations, but only by specifying exactly what type of polymorphic associations you want. To do so you must use the :source_type option. In most cases you will have to use the :source option, since the association name will not match the interface name used for the polymorphic association:
class User < ActiveRecord::Base
has_many :comments
has_many :commented_timesheets, :through => :comments, :source => :commentable,
:source_type => "Timesheet"
has_many :commented_billable_weeks, :through => :comments, :source => :commentable,
:source_type => "BillableWeek"
It's verbose and the whole scheme loses its elegance if you go this route, but it works:
User.first.commented_timesheets
I hope I helped!

multiple joins using activerecord in rails

I'm building a small twitter style microblogging service where users can follow other users and get a feed of their messages
I have the following models:
class Follow < ActiveRecord::Base
belongs_to :follower, :class_name => "User"
belongs_to :followee, :class_name => "User"
end
class User < ActiveRecord::Base
has_many :follows, :foreign_key => 'follower_id',
:class_name => 'Follow'
has_many :followers, :through => :follows
has_many :followed, :foreign_key => 'followee_id',
:class_name => 'Follow'
has_many :followees, :through => :followed
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
end
To get a feed for the current user, I want to perform the following SQL query:
SELECT * FROM follows JOIN users JOIN messages WHERE follows.follower_id = current_user.id AND follows.followee_id = users.id AND users.id = messages.user_id;
What is the correct ActiveRecord way of doing this?
Not sure what you're looking for, but here is my suggestion:
I assume that you have other purposes for that Follow class, otherwise I don't see the purpose of it.
The "correct way" (i.e. my completely subjective way) to do it would actually be something like this:
class User < ActiveRecord::Base
has_and_belongs_to_many :followers, :foreign_key => 'followed_id',
:class_name => 'User', :association_foreign_key => 'follower_id',
:include => [:messages]
has_and_belongs_to_many :follows, :foreign_key => 'follower_id',
:class_name => 'User', :association_foreign_key => 'followed_id'
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
end
Then create the following table:
create_table :users_users, :id => false do |t|
t.integer :followed_id
t.integer :follower_id
end
And you're set:
followed = User.find :first
follower = User.find :last
followed.followers << follower
followed.followers.first.messages
followed.followers.first.followers.first.messages # etc...
But from what I make it, you want to show all the messages from all the followers at the same time.
This should be possible to achieve by adding
has_and_belongs_to_many :followed_messages, :foreign_key => 'follower_id',
:class_name => 'Message', :association_foreign_key => 'followed_id'
to the User class, but I don't know how correct that way would be. Or it might be possible to achieve with association extensions but there I can't really give any examples.
Update:
By changing the :class_name, it will associate it with the Message.id, didn't think about that so it will not be correct in this way.
So the only "nice" option is to go through the User class like in the first example.
The only other options I can see is either the association extensions (which I can't give you an example for) or perhaps using a finder statement.
has_many :followed_messages, :class_name => 'Message',
:finder_sql => 'select * from messages where user_id in(select followed_id from users_users where follower_id = #{id})'
You probably have to customize that sql statement to get everything to work, but at least you should get the picture :)
Keijro's arrangement would work better, though if you need the Follow table, then you can execute the SQL query you specified as follows:
Follow.all(:joins => { :messages, :users }, :conditions => { "follows.follower_id" => current_user.id, "follows.followee_id" => "users.id", "users.id" => "messages.user_id"} )