rails 3 searching and ordering by two fields - ruby-on-rails

I have a list of questions with different positions and question group ids
how would i find all the questions with one id and pick out the one with the biggest position number.
is this close
<%= #question = Question.maximum('position', :conditions => {'question_group_id' => question_group_id'}) %>
using any of the answers i get
#<Question:0x3fe85c0>
how do i turn that into something we can read

First of I see an erb tag (<% %>) and the operation you perform will be better on your controller.
Now the query (in the controller) should be
#question = Question.where(question_group_id: question_group_id).
order("position desc").
limit(1). #because you only need one record!
first

You can do that with the following query.This will defiantly work for you.
#question = Question.where('question_group_id = ?',question_group_id).order("position desc").first

#question = Question.where(:question_group_id => question_group_id).order("position desc").first

First of all, I would put the query on the Model as a named scope and not in the Controller.
Given that, on your Question model:
scope :highest_position_by_question_group_id, lambda { |question_group_id|
where(question_group_id: question_group_id).order('position DESC').first
}
And on your Controller:
#question = Question.highest_position_by_question_group_id(question_group_id)

Related

Ruby on Rails 5: Find index of post_id and display in view (post # of n)

I have a resource :posts, which I show one at a time in show.html.erb
Suppose I have ten posts, each with an :id going from 1-10. If I delete post #2, then my posts will be 1,3,4,5,6,7,8,9,10. If I create ten posts and delete them all, then the next post :id would be [1,3..10,21] but I would only have 11 posts.
I want to show the post number that's in the application and put it in the view against a total number of posts. So if you were looking at post #3, it might have an :id of 3, but it is post #2 in the database.
Here's what I tried so far:
posts_controller.rb
def show
...
#post = Post.friendly.find(params[:id])
#total_posts = Post.all.count.to_i
#posts_array = Post.pluck(:id).to_a
...
end
views/posts/show.html.erb
<%= #post.id %> of <%= #total_posts %> /
models/post.rb
def next
Post.where("id > ?", id).order(id: :asc).limit(1).first
end
def prev
Post.where("id < ?", id).order(id: :desc).limit(1).first
end
However, showing the :id of a resource is a security issue so I don't know how to do it better.
How can I make it so the show.html.erb view only shows the current index order of the total amount of resources as compared to the post_id?
An efficient way to do this could be
# app/controllers/posts_controller.rb
def show
#post = Post.friendly.find(params[:id])
#total_posts = Post.count
#post_index = Post.where("id <= ?", #post.id).count
end
# app/views/posts/show.html.erb
. . .
<%= #post_index %> of <%= #total_posts %>
. . .
You should avoid loading all posts (or even their id) if you can. This will become more and more expensive as the number of posts grows and will eventually become a bad bottleneck for performance.
If you're trying to find the 'array index' of a record (so to speak) you can do this:
Agency.order(id: :asc).offset(params[:index]).limit(1)
You don't really want to do any other way because then it will load EVERY record into rails which will be very slow. It's better to ask the database for only a single record (which is what 'offset' does). Just replace params[:index] with whatever the name of the params is, whether its params[:id], etc.
I did just want to address one thing you said:
However, showing the :id of a resource is a security issue so I don't know how to do it better
That's not a security issue. The app should be designed in a way where the ID of a resource is not special or "secret." If you have an ID of a record, your controller should work such that it "authorizes" certain actions and won't let you do something you're not supposed to (like a user deleting a post).
If you REALLY need to do this, then just hide the ID and use a slug instead, like example.com/this-is-a-post-slug. This can be done quite easily
Edit To answer your specific question...
ids = Agency.order(id: :asc).pluck(:id)
#post_index = ids.find_index(#post.id)
#next_post = ids[#post_index + 1]
#prev_post = ids[#post_index - 1]
You can now use #post_index in your view.
Note: #prev_post and #next_post will be nil when the page doesn't exist (i.e. the "next post" when you're on the last page), so you will need to check that.
Just try it:
def show
...
#post = Post.friendly.find(params[:id])
#total_posts = Post.count # this will return integer type data
#posts_array = Post.pluck(:id) # you don't need to_a as .pluck returns array
...
For the next part you could write:
def next
self.class.where("id > ?", id).limit(1).first # this use of id is secured.
end
def prev
self.class.where("id < ?", id).order(id: :desc).limit(1).first
end

Ruby on Rails 4 fields_for number of repetitions

I would like to display a form with four nested fieldsets for associated objects. The only way I've found is to override the initialize method and define four associations:
RUBY
def initialize(attributes = {})
super
4.times { items << Item.new }
end
and then display nested fields normally:
HAML
= f.fields_for :items do |item|
= render 'item_fields', f: item
This is not working when I try to edit objects that already exist and have fewer number of associated items.
Any help will be appreciated.
MORE INFO:
Order has_many items
OrderSet has_many orders
Orders are added through the cocoon gem (there is at least one order in each set)
There should always be four items for each order. But when there are less items I don't want to save empty records, instead I would like to just display remaining items as empty.
The initialize is not the place as it is executed every time a new Order instance is created, this means: also when retrieving an existing order from the database.
Imho the view is also not the optimal place.
I would solve this in the controller:
def new
#order = Order.new
4.times { #order.items.build }
end
and then you can just leave your model/view as they were originally.
If you always want to show 4 nested items, you can do something similar in the edit action (to fill up to 4)
def edit
#order = Order.find(params[:id])
(#order.items.length...4).each { #order.items.build }
end
In my personal opinion this is cleaner then doing it in the view.
[EDIT: apparently it is a double nested form]
So, in your comment you clarified that it is a double-nested form, in that case, I would use the :wrap_object option as follows (it gets a bit hard to write a decent example here, without you giving more details, so I keep it short and hope it is clear). I am guessing you have a form for "something", with a link_to_add_association for :orders, and that order needs to have several (4) items, so you could do something like:
= link_to_add_association('add order', f, :orders,
:wrap_object => Proc.new { |order| 4.times { order.items.build}; order })
Before your f.fields_for in your view, or even in your controller, you can check the length of .items() and create new objects as required:
(o.items.length...4).each { f.object.items << Item.new}
= f.fields_for :items do |item|
= render 'item_fields', f: item

How can I combine the results of different variables into one variable?

I have following four variables in my controller index action which are retrieving data from different models as follows:
#forum = Forum.where(:user_id => #users.collect(&:user_id)).all
#poll=Poll.where(:created_by => #users.collect(&:user_id)).all
#article = Article.where(:user_id => #users.collect(&:user_id)).all
#jobpost = Jobplacement.where(:user_id => #users.collect(&:user_id)).all
I want to join all these variables' data into a single variable #post. How can I do this?
It is not good to have different type of objects in single collection.
But as you asked try
#post = [#forum,#forum,#article,#jobpost].flatten
Update:
I wrote this answer when I was a newbie in Ruby. When I look this answer I can not control my smile. The purpose of the flatten is to make a single array from the nested arrays. The answer does not relate to the question. But I am surprised about the upvotes :)
Put them in a hash:
#post = Hash.new
#post['forum'] = Forum.where(:user_id => #users.collect(&:user_id)).all
#post['poll'] = Poll.where(:created_by => #users.collect(&:user_id)).all
#post['article'] = Article.where(:user_id => #users.collect(&:user_id)).all
#post['job_placement'] = Jobplacement.where(:user_id => #users.collect(&:user_id)).all
They are not joined, but they are in one single variable. You can access them whenever you want, and do with them whatever you want.
Something like this:
conditions = { :user_id => #users } # assuming primary_key is set correctly
# in the User model
#post = Forum.where( conditions ).all +
Poll.where( conditions ).all +
Article.where( conditions ).all +
Jobplacement.where( conditions ).all
Or if you want to get fancy:
models = [ Forum, Poll, Article, Jobplacement ]
#post = models.reduce [] do |records, model|
records.push *model.where( :user_id => #users ).all
end
Note: .all might be unnecessary in both cases since it's usually called automatically by Rails when necessary, but I'm not certain.
I think you need like view model concept. Create a simple model class without inherit from ActiveRecord::Base and add all objects as attributes in the new class and initialize that.
class Post
attr_accessor :forum, :poll, :article, :jobpost
def initialize(forum,poll,article,jobpost)
#forum = forum
#poll = poll
#article = article
#jobpost = jobpost
end
end
In the controller action add the following;
#post = Post.new(#forum,#poll,#article,#jobpost)

Moving of will_paginate to model

On my Question model I have some scopes
scope :recent, order("created_at DESC")
scope :approved, where("status = ?", "approved")
scope :answered, approved.recent.where("answers_count > ?", 0)
On my question controller I'm retrieving questions using the scopes
example 1:
#questions = Question.approved.recent
example 2:
#questions = User.find(session[:user_id]).topics.map { |t| t.questions.approved.recent }.flatten.uniq
I'm trying to put will_paginate on my model to make things easier on the controller but the 2nd example is very tricky as it is using mapping to retrieve questions according to preferences.
I've tried to add this on my model
def self.pagination(page = 1)
self.paginate(:page => page, :per_page => 5)
end
and then on my controller I have
#questions = Question.approved.recent.pagination.(params[:page])
That works fine for the 1st example but I Dont know how to implement that on the 2nd example
Any hints?
This looks like Rails 3. Be sure to use the ~> 3.0.pre2 version of the will_paginate gem.
You can use the paginate method at the end of your chain of scopes. For example, your "example 1" would be:
#questions = Question.approved.recent.paginate(:page => params[:page], :per_page => 20)
I see you created a custom method (pagination) to wrap this pattern, but it's best that you keep this syntax in original form for now, especially since you're dealing with scopes and Relation objects in Rails 3 and will_paginate doesn't have proper support for this yet (but it's coming).
In your "example 2" it seems you only need to fetch the first few recent questions from each topic and that you won't perform a full-blown pagination here (like, going to page 2 and forward). You don't have to use the paginate method here; you can simply use ActiveRecord's limit:
current_user = User.find(session[:user_id])
#questions = current_user.topics.map { |topic|
topic.questions.approved.recent.limit(5).to_a
}.flatten.uniq

In Rails, what is the best way to update a record or create a new one if it doesn't exist?

I have a create statement for some models, but it’s creating a record within a join table regardless of whether the record already exists.
Here is what my code looks like:
#user = User.find(current_user)
#event = Event.find(params[:id])
for interest in #event.interests
#user.choices.create(:interest => interest, :score => 4)
end
The problem is that it creates records no matter what. I would like it to create a record only if no record already exists; if a record does exist, I would like it to take the attribute of the found record and add or subtract 1.
I’ve been looking around have seen something called find_or_create_by. What does this do when it finds a record? I would like it to take the current :score attribute and add 1.
Is it possible to find or create by id? I’m not sure what attribute I would find by, since the model I’m looking at is a join model which only has id foreign keys and the score attribute.
I tried
#user.choices.find_or_create_by_user(:user => #user.id, :interest => interest, :score => 4)
but got
undefined method find_by_user
What should I do?
my_class = ClassName.find_or_initialize_by_name(name)
my_class.update_attributes({
:street_address => self.street_address,
:city_name => self.city_name,
:zip_code => self.zip_code
})
Assuming that the Choice model has a user_id (to associate with a user) and an interest_id (to associate with an interest), something like this should do the trick:
#user = User.find(current_user)
#event = Event.find(params[:id])
#event.interests.each do |interest|
choice = #user.choices.find_or_initialize_by_interest_id(interest.id) do |c|
c.score = 0 # Or whatever you want the initial value to be - 1
end
choice.score += 1
choice.save!
end
Some notes:
You don't need to include the user_id column in the find_or_*_by_*, as you've already instructed Rails to only fetch choices belonging to #user.
I'm using find_or_initialize_by_*, which is essentially the same as find_or_create_by_*, with the one key difference being that initialize doesn't actually create the record. This would be similar to Model.new as opposed to Model.create.
The block that sets c.score = 0 is only executed if the record does not exist.
choice.score += 1 will update the score value for the record, regardless if it exists or not. Hence, the default score c.score = 0 should be the initial value minus one.
Finally, choice.save! will either update the record (if it already existed) or create the initiated record (if it didn't).
find_or_create_by_user_id sounds better
Also, in Rails 3 you can do:
#user.choices.where(:user => #user.id, :interest => interest, :score => 4).first_or_create
If you're using rails 4 I don't think it creates the finder methods like it used to, so find_or_create_by_user isn't created for you. Instead you'd do it like this:
#user = User.find(current_user)
#event = Event.find(params[:id])
for interest in #event.interests
#user.choices.find_or_create_by(:interest => interest) do |c|
c.score ||= 0
c.score += 1
end
end
In Rails 4
You can use find_or_create_by to get an object(if not exist,it will create), then use update to save or update the record, the update method will persist record if it is not exist, otherwise update record.
For example
#edu = current_user.member_edu_basics.find_or_create_by(params.require(:member).permit(:school))
if #edu.update(params.require(:member).permit(:school, :majoy, :started, :ended))

Resources