Really easy Rails Active-Record Associations question - ruby-on-rails

I have 2 models:
class Mission < ActiveRecord::Base
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :missions
end
And I have a complex Mission find statement:
#missions = Mission.send(#view, level).send(#show).search(#search).paginate :page => params[:page], :order => actual_sort, :per_page => 50
I'd like to add to my query the possibility to search for a specific category too.
I tried this but it does not work:
#missions = Mission.send(#view, level).send(#show).send(:category, #category).search(#search).paginate :page => params[:page], :order => actual_sort, :per_page => 50
Rails says that Mission has not a .category method. How would you solve this?
Thanks,
Augusto

OH ... MY ... GOD
Are you sure this is the best way to be doing this?
I don't think people will be able to help you if you don't explain a bit more, but I highly doubt that you couldn't write your statement like so:
#missions = Mission.select("missions.level ...,category.attr ...").where(["missions.level = ? ...", level ...]).includes(:category).where(["categories.field = ?", ...]).paginate(...)
Obviously the elipses (...) mean generally etc.
This is a working example on one of my projects in testing:
i = Item.where("items.name like '%Coupon%'").includes(:category).where(["categories.name = ? ",'Category 2'])

try performing the where selection on category_id

Related

How do date comparisons work in ActiveRecord?

I am new to Rail and trying to make a query in ActiveRecord. I am trying to get all of the records with the status of 'Landed', that are over 60 days old. My query works up to the point of getting all of the projects with the status of 'Landed'. When I add in the last condition of "created_at < ? ", then I always get an empty relation. I know that I have projects that fit that description, so I am doing something wrong in my query and dont understand. I believe my error is in the date comparison, but I am not sure.
1. Projects
belongs_to :status
has_many :project_status_histories
2. Status
has_many :projects
has_many :project_status_histories
3. Project_Status_Histories
belongs_to :status
belongs_to :project
Project.find(:all, :joins => [:project_status_histories, :status], :conditions => {:projects => {:status_id => Status.where(:name => 'Landed').first.id }, :project_status_histories => {:created_at => ["created_at < ?", (Date.today - 60.days)]}})
I have tried to build the query, step by step, with the dbconsole and am not having any luck. Thanks for all the help in advance.
I don't think it's the date arithmetic. One nice way to do this would be with named scopes. Add the following to project.rb:
scope :landed, joins(:status).where('statuses.name' => 'Landed')
scope :recent, lambda \
{ joins(:project_status_histories) \
.where('project_status_histories.created_at < ?', Date.today - 60.days) }
Then you can retrieve the relevant records/objects with:
Project.landed.recent
This worked for me in my test. You should also check out the rails guide, from which I stole most of this:
http://guides.rubyonrails.org/active_record_querying.html#scopes
Your query is a little bit complicated...
I would rather do it like this:
Project.where("status.name = ? AND project_status_histories.created_at < ?", "Landed", Time.now.day - 60.days)
I think this should work better. Let me know if it doesn't, maybe I've wrote something wrong, unfortunately I can't test it right now...
[Edit]
You also might want to see what is the generated SQL, use the "explain" method for that, just add it to the end of your query and print the result, for instance with your query:
Project.find(:all, :joins => [:project_status_histories, :status], :conditions => {:projects => {:status_id => Status.where(:name => 'Landed').first.id }, :project_status_histories => {:created_at => ["created_at < ?", (Date.today - 60.days)]}}).explain

Adding another condition to existing named_scope

I am working on a Rails 2.3.9 app and my question involves both a self referencial relationship and a named_scope. This application allows users to log and share workouts. A workout can be public or private and that is designated by #workout.public == 1.
I allow users to 'follow' people. So on a current_user's dashboard I display all public workouts from users that current_user follows with the following code:
/dashboard/index.html.erb
<% current_user.friends_workouts.each do |workout| %>
<%= link_to (workout.title), workout %> <br/>
by <%= link_to (workout.user.username), workout.user %> - <%= time_ago_in_words(workout.created_at)%> ago</p>
<% end %>
user.rb
def friends_workouts
#friends_workouts ||= Workout.current.public_workouts.find_all_by_user_id(self.friends.map(&:id), :order => "created_at DESC", :limit => 3)
end
workout.rb
named_scope :public_workouts, :conditions => {:public => 1 }, :order => "created_at DESC"
I now want to add a condition to this scope as I am adding another level of sharing. Users can associate to a "box" (a gym really) through a "membership" model. So if the current_user belongs_to the same "box" as a user they follow, they should not only see the workouts marked public but also workouts where #workout.box_only == 1.
How can I affect the above to include all public workouts from followed users AND workouts from followed users where #workout.box_only == 1 and #workout.user.membership.box_id == current_user.membership.box_id. I know that syntax is incorrect but you get my point (hopefully).
UPDATE:
It also needs to be considered that :public_workouts is being called from pages that don't require a logged_in? user so in that case if the scope is trying to reference current_user it will throw an error.
UPDATE 2:
:user has_many :memberships
I believe something like the following should do it for you:
named_scope :public_workouts,
:joins => ", user, membership"
:conditions =>
"workouts.public = 1 or
membership.box_id = #{current_user.membership.box_id}",
:group => "workouts.id",
:order => "workouts.created_at DESC"
You would have to play around with this for a bit. The hard part every time I try something like this is to get the OR conditions correct. You want to get all public and those where the joined membership.box_id matches regardless of public being 1.
Edit: Admittedly this is perhaps not the most ruby way of building a query and I haven't tested it properly but something like below could also be made to work.
def self.public_workouts
query = Workout.joins(:user => { :membership })
if current_user
query.where('memberships.box_id = ? or workouts.public = 1', current_user.membership.box_id) unless current_user.membership.box_id.nil?
else
query.where('workouts.public = 1')
end
query.group('workouts.id')
query.order("workouts.created_at DESC")
return query
end
Edit2
Another alternative could be to create two separate scopes and create a class method that combines the two scopes. This class method would then be used in the view.
named_scope :box_workouts,
:joins => ", user, membership"
:conditions => "memberships.box_id = #{current_user.membership.box_id}"
:group => "workouts.id",
:order => "workouts.created_at DESC",
:select "workouts"
named_scope :public_workouts,
:conditions => :public => 1
:order => "workouts.created_at DESC"
def self.public_box_workouts
return box_workouts.merge(public_workouts).limit(3) if current_user
return public_workouts.limit(3)
end
Edit3 Not so hard, I believe something like below will work.
def self.box_and_public_workouts(user)
return public_workouts if user.nil? or user.memberships.blank?
return public_workouts + box_workouts(user.memberships.map(&:box_id))
end
named_scope :box_workouts, lambda { |box_ids| { :conditions => ['box_id IN (?)', box_ids], :order => 'created_at DESC' } }
Apologies for taking so long. I was missing confused with how the "old" way of querying the database. I went right for Rails3 :)
Anyway, I didn't want to commit anything so I tried to fork it to send a pull request but github is being rude to me tonight. Might just copy from here then.

Can I add the limit the result of object return in Ruby on rails?

For Example, I want to know the User have many posts. So, I can get back post using this :
#user.posts
but I don't want to get all the posts back. I would like to limite the result, for example, top ten created, or may be sorted by some column. How can I do so? Thank you.
You can always make a generic scope to handle the limit, such as putting this in an initializer:
class ActiveRecord::Base
named_scope :limit, lambda { |*limit| {
:limit => limit[0] || 10,
:offset => limit[1]
}}
end
This makes limiting queries easy:
# Default is limited to 10
#user.posts.limit
# Pass in a specific limit
#user.posts.limit(25)
# Pass in a specific limit and offset
#user.posts.limit(25, 25)
For something more robust, you might want to investigate will_paginate.
Try this:
#user.posts(:limit => 10, :order => "created_at DESC")
http://guides.rubyonrails.org/active_record_querying.html
You should take a look at the options available for the has_many association.
You could try something like this:
class User < ActiveRecord::Base
has_many :posts
has_many :top_ten_posts, { :class_name => "Post", :order => :some_rating_column, :limit => 10 }
end

Ruby on Rails: using nested named_scopes

I just got referred to stackoverflow by a friend here to help with a problem I am having. I am fairly new to ruby on rails and I am working on a collaborative project where we have a script (medal_worker.rb) that is scheduled to run at a fixed intervals to award people various medals based on various participation and success on our website. One of the new medals I am working on rewards people for "milestones". For the purpose of this problem, let's say we want to give them medals when they make 100, 1000, and 10000 comments. I would like to do this by using named_scopes from the User model (user.rb) to give me filtered lists of the users I am looking for.
My question is: How do I find the users who do not have the respective medals for the respective milestone comment level (preferably using the named_scopes from the User model)?
Here is an exerpt from my model_worker.rb file:
def award_comment_milestone(comments)
users = Users.frequent_comment_club_members(comments).not_awarded_medal(Medal.find_by_id(medal.id))
for user in users do
award_medal(medal, nil, user) if #award
end
end
Here is where I am at with the named_scopes in the user model (user.rb):
named_scope :frequent_comment_club_members, lambda { |*args|
{:include => comment_records, :conditions => ['comment_records.comment_type = ? and comment_records.comments >= ?', 'User', (args.first || 0)]}
}
named_scope :not_awarded_medal, lambda { |medal|
{:include => :awards, :conditions => ['awards.medal_id not in (select awards.medal_id from awards where awards.medal_id = ?)", medal.id] }
}
This is not working as I would like, but I don't know if the problem is in the named_scopes or how I am passing arguements or what. Thanks.
Your named_scopes look fine. Except you are starting with a single apostrophe and ending with a double apostrophe in the not_awarded_medal condition statement.
EDIT:
Take it back. Your not_awarded_medal named_scope is off.
Try something like this:
named_scope :not_awarded_medal, lambda { |medal_id|
{ :include => :awards,
:conditions => [
"? not in (select awards.id from awards where awards.user_id = users.id)", medal_id
]
}
}
untested
Now this is assuming that you have the following relationships:
User: has_many :awards
Award: belongs_to :user
If you are using has_many :through then you are going to have to change the SQL to look at the join (users_awards) table.
--
But I do see a couple of things in the award_comment_milestone function.
What is the parameter coming into award_comment_milestone? Is it an array of comments or is it a count of comments? Also where is medal defined?
If comments is an array then you need to pass comments.length into frequent_comment_club_members. If it's the count then I would rename it to comments_count so the next person can understand the logic more quickly.
Some general observations:
not_awarded_medal should just take a medal_id and not the whole object (no need to do multiple queries)
Why are you doing Medal.find_by_id(medal.id)? You already have the medal object.

Advanced find in Rails

I really suck at Rails' finders besides the most obvious. I always resort to SQL when things get more advanced than
Model.find(:all, :conditions => ['field>? and field<? and id in (select id from table)', 1,2])
I have this method:
def self.get_first_validation_answer(id)
a=find_by_sql("
select answers.*, answers_registrations.answer_text
from answers_registrations left join answers on answers_registrations.answer_id=answers.id
where
(answers_registrations.question_id in (select id from questions where validation_question=true))
and
(sale_registration_id=#{id})
limit 1
").first
a.answer_text || a.text if a
end
Can someone create a find method that gets me what I want?
Regards,
Jacob
class AnswersRegistration < ActiveRecord::Base
has_many :answers
end
id = 123
the_reg = AnswersRegistration.first(
:joins => :answers,
:conditions => '(question_id in (select id from questions where validation_question = true)) and (sale_registration_id = ?)', id)
(untested)
Just use binarylogic's Searchlogic gem if that satisfies your need.
Here you go: http://github.com/binarylogic/searchlogic
Sometimes AR will choke on complicated nested conditions, but in theory you should be able to do this:
AnswersRegistration.first(:conditions => { :question => { :validation_question => true },
:sale_registration_id => id },
:include => :answer)

Resources