(Warning: Clueless Rails Newbie!)
In my show.html.erb for my albums view, I call a public method in my albums controller:
<% albums_feature = find_albums_with_feature(feature.id) %>
It generates a NoMethodError.
So I copied the method into my Album model and tried calling it from the view as:
<% albums_feature = Album.find_albums_with_feature(feature.id) %>
But this also gets a NoMethodError.
Where should I define this method?
For what it's worth, the method looks like this:
def find_albums_with_feature(feature_id)
albums_for_feature = Albums.find_by_sql(
["select al.* from albums al, albums_features alfe
where al.id = alfe.album_id
and alfe.feature_id = ?", feature_id])
end
If you want to have method that is accesible from view, you have few options:
put it in the model
put it in the helper
put it in the controller and add a line "helper_method :find_albums_with_feature"
But I think you can do it better. Firstly, don't put any finding methods in view. Put it in the controller. Secondly, you don't need to specify your own finding method. Probably you have something like this in your models:
class Album << ActiveRecord::Base
has_many :albums_features
has_many :features, :through => :albums_features
end
class AlbumsFeature << ActiveRecord::Base
belongs_to :album
belongs_to :feature
end
class Feature << ActiveRecord::Base
has_many :albums_features
has_many :albums, :through => :albums_features
end
With it, you can find albums with specific feature like this:
#feature = Feature.find(id)
#albums = #feature.albums
or
#albums = Feature.find(id).albums
and it should be in your controller. In view you should only display results.
If you are looking for more informations about associations, take a look here: http://guides.rubyonrails.org/association_basics.html. I think it's the best place where you can learn about Rails - at least it is for me.
In the Album model. Needs self in front though:
def self.find_albums_with_feature(feature_id)
Related
Let's say I have the following relationship in my Rails app:
class Parent < ActiveRecord::Base
has_many :kids
end
class Kid < ActiveRecord::Base
belongs_to :parent
end
I want parents to be able to see a list of their chatty kids, and use the count in paginating through that list. Here's a way to do that (I know it's a little odd, bear with me):
class Parent < ActiveRecord::Base
has_many :kids do
def for_chatting
proxy_association.owner.kids.where(:chatty => true)
end
end
end
But! Some parents have millions of kids, and p.kids.for_chatting.count takes too long to run, even with good database indexes. I'm pretty sure this cannot be directly fixed. But! I can set up a Parent#chatty_kids_count attribute and keep it correctly updated with database triggers. Then, I can:
class Parent < ActiveRecord::Base
has_many :kids do
def for_chatting
parent = proxy_association.owner
kid_assoc = parent.kids.where(:chatty => true)
def kid_assoc.count
parent.chatty_kids_count
end
end
end
end
And then parent.kids.for_chatting.count uses the cached count and my pagination is fast.
But! Overriding count() on singleton association objects makes the uh-oh, I am being way too clever part of my brain light up big-time.
I feel like there's a clearer way to approach this. Is there? Or is this a "yeah it's weird, leave a comment about why you're doing this and it'll be fine" kind of situation?
Edit:
I checked the code of will_paginate, seems like it is not using count method of AR relation, but i found that you can provide option total_entries for paginate
#kids = #parent.kids.for_chatting.paginate(
page: params[:page],
total_entries: parent.chatty_kids_count
)
This is not working
You can use wrapper for collection like here
https://github.com/kaminari/kaminari/pull/818#issuecomment-252788488,
just override count method.
class RelationWrapper < SimpleDelegator
def initialize(relation, total_count)
super(relation)
#total_count = total_count
end
def count
#total_count
end
end
# in a controller:
relation = RelationWrapper.new(#parent.kids.for_chatting, parent.chatty_kids_count)
There are articles and comments.
article has_many :comments
comment belongs_to :article
I want to get all comments where value_id is equal to value_id attribute in the article that comment belongs_to.
class Comment < ActiveRecord::Base
belongs_to :article
def self.value_comments
where(value_id: self.article.value_id)
end
end
I get an error:
undefined method `article' for #<Class:0x007fd2a7e46d18>
controller
#value_comments = Comment.value_comments.where(user_id: current_user.id).order("created_at desc")
Having read your question again, my understanding is that you want to find all Comments whose value_id matches the value_id of their associated Article.
Your code is nearly correct - you need a few more parts to get this to work. You need to join your Comment table to your Article table using joins. Then, refer to the column in a where function using arel_table.
So you should end up with something like this:
def self.value_comments
joins(:article).where(self.arel_table[:value_id].eq Article.arel_table[:value_id])
end
You could also consider using sexy_scopes to make it easier to access your columns.
I have a migration and model with a table called medications. I need to pick a specific row from the medications table. I also am trying to filter out all medications that don't have the current user's id.
Here is the current code I have.
Medication.find(:name, :conditions => { :user_id => current_user.id }, :order => "Medication.name")
I know this isn't complete, but any help would be greatly appreciated.
You can load the first medication for a specific user_id like this (assuming that your medications table has an user_id):
Medication.where(user_id: current_user.id).order(:name).first
When our User model has a belongs_to :medications it can be simplified to:
current_user.medications.order(:name).first
When you want to load the e.g. 5th medication just add an offset of 4:
current_user.medications.order(:name).offest(4).first
Or load all medications and iterate through them:
current_user.medications.limit(10).each do |medication|
puts medication.name
end
When you want to output the first ten medications on a website you would do something like this:
# in the controller
#medications = current_user.medications.order(:name).limit(10)
# in the view
<ul>
<% #medications.each do |medication| %>
<li><%= medication.name %></li>
< end %>
</ul>
The finder syntax you use is deprecated and was replaced in Rails 4. See Rails Guide about querying the database.
This is a perfect use case for a has_many :through association if you don't already have it set up.
class User < ActiveRecord::Base
has_many :prescriptions # or whatever
has_many :medications, :through => :prescriptions
end
class Prescription < ActiveRecord::Base
belongs_to :user
belongs_to :medication
end
class Medication < ActiveRecord::Base
has_many :prescriptions
has_many :users, :through => :prescriptions
end
Now you can do stuff like #user.medications to retrieve only that user's medications, #user.medications.find(params[:medication_id] to find a specific one within a user's assigned medications, and #user.medications << Medication.find_by(name: 'Aspirin') to add a medication to a user, and so on.
This is a basic overview of this technique, but it's a basic Rails concept so there's plenty of information on use cases close to whatever you may be trying to do.
I fixed the problem and I have decided to post the answer in case anybody else seems to have a similar problem.
I ended up not putting anything in my controller or adding anything new to my models. I just used this line of code in the view.
<%= Medication.offset(0).where(:user_id => current_user.id).pluck(:name).first %>
I couldn't have done it without the support of everyone who posted, Thank you!
I have such two models:
class Article < ActiveRecord::Base
belongs_to :articles_type
end
class ArticlesType < ActiveRecord::Base
has_many :articles
end
and in controller i write:
#articles = Article.where(article_type_id: params[:id])
and in view(haml) i try:
= #articles.articles_type.id
= #articles.articles_types.id
= #articles.first.articles_type.id
= #articles.first.articles_types.id
how could i display this articles_type.id but for only first row?
now i get
undefined method `articles_type'
but why? what i do wrong? how to display nested model id?
#articles will be a collection of items, not just a single one (because you used the where method). You will have to do:
#articles.first.articles_type_id
(Also note that you don't have to do .articles_type.id, because the #articles.first already has the ID of the type)
The undefined method message is because #articles does not have an articles_type method. You have to get to a single instance of an Article in order to use that method. You could do that with a call to #articles.first or by iterating on the collection.
= #articles.first.articles_type.id
is the line you want to use.
Looks like you've got your logic backwards.
Based on your models, an article belongs to an article_type.
#articles.first.article_type.id
# OR #articles.first.article_type_id
Just looks like you're incorrectly pluralizing .article_types when it should be .article_type.
I have 2 equal-access models: Users and Categories
Each of these should have the standard-actions: index, new, create, edit, update and destroy
But where do I integrate the associations, when I want to create an association between this two models?
Do I have to write 2 times nearly the same code:
class UsersController << ApplicationController
# blabla
def addCategory
User.find(params[:id]).categories << Category.find(params[:user_id])
end
end
class CategoriessController << ApplicationController
# blabla
def addUser
Category.find(params[:id]).users << User.find(params[:user_id])
end
end
Or should I create a new Controller, named UsersCategoriesController?
Whats the best practice here? The above example doens't look very DRY.... And a new controller is a little bit too much, I think?
Thanks!
EDIT:
I need to have both of these associations-adding-functions, because f.e.
#on the
show_category_path(1)
# I want to see all assigned users (with possibility to assign new users)
and
#on the
show_user_path(1)
#I want to see all assigned categories (with possibility to assign new categories)
EDIT:
I'm taking about a HBTM relationship.
If you have a situation where you need to do this with has_and_belongs_to_many, you could take the approach you are currently using, or you could build this into your existing update actions.
When you add a habtm relationship, you will get an additional method on your classes...
class User < ActiveRecord::Base
has_and_belongs_to_many :categories
end
With this, you can do this:
user = User.find(params[:id])
user.category_ids = [1,3,4,7,10]
user.save
The categories with those ids will be set. If you name your form fields appropriately, the update can take care of this for you if you want to use checkboxes or multiselect controls.
If you need to add them one at a time, then the methods you've built in your original post are reasonable enough. If you think the repetition you have is a code smell, you are correct - this is why you should use the approach I outlined in my previous answer - an additional model and an additional controller.
You didn't mention if you are using has_and_belongs_to_many or if you are using has_many :through. I recommend has_many :through, which forces you to use an actual model for the join, something like UserCategory or Categorization something like that. Then you just make a new controller to handle creation of that.
You will want to pass the user and category as parameters to the create action of this controller.
Your form...
<% form_tag categorizations_path(:category_id => #category.id), :method => :post do %>
<%=text_field_tag "user_id" %>
<%=submit_tag "Add user" %>
<% end %>
Your controller...
class CategorizationsController < ApplicationController
def create
if Categorization.add_user_to_category(params[:user_id], params[:category_id])
...
end
end
then your categorization class...
class Categorization
belongs_to :user
belongs_to :category
def self.add_user_to_category(user_id, category_id)
# might want to validate that this user and category exist somehow
Categorization.new(:user_id => user_id, :category_id => category_id)
Categorization.save
end
end
The problem comes in when you want to send the users back, but that's not terribly hard - detect where they came from and send them back there. Or put the return page into a hidden field on your form.
Hope that helps.