ruby on rails - Ordering two models - ruby-on-rails

I have two models image.rb and story.rb
I am trying to order them together.
stories_controller.rb looks like this:
def index
#stories = Story.all.order(:cached_votes_total => :desc)
#images = Image.all.order(:cached_votes_total => :desc)
#combined = (#stories + #images).sort_by {|record| record.created_at}
end
private
def story_params
params.require(:story).permit(:title, :content, :category)
end
images_controller.rb looks like this:
private
def image_params
params.require(:image).permit(:title, :image, :image_file_name, :category)
end
In my index.html.erb im tryign to order them both but i run into undefined method errors because they have different parameters.
<% #combined.each do |s| %>
...
<% end %>
is there a way to fix this?

This is an wrong approach, combining two models is not recommended, you should use model association for this.. For example
# Image Model
class Image < ActiveRecord::Base
belongs_to :story
end
# Image Model
class Story < ActiveRecord::Base
has_one :image
end
In the controller..
def index
#stories = Story.find(:all, :include => :image).order(:cached_votes_total => :desc)
end
and finally in the view
<% #stories.each do |story| %>
# here you can access story and story.image
...
<% end %>

Related

how to retrieve data from the database using the has_many association

I have
message table
and
message_pictures
table(It belongs to the message table) and what I want to do is retrieve each messsge pictures (if there is any) from the
message_controller
.
How can I do it. I have surfed the web but found little scanty explanations on it.
This is the message class(model)
class Message < ApplicationRecord
belongs_to :user
has_many :message_pictures, dependent: :destroy
accepts_nested_attributes_for :message_pictures
end
This is the message_pictures class(model)
class MessagePicture < ApplicationRecord
belongs_to :message, optional: true
mount_uploader :message_pictures, PictureUploader
end
and this is the index method of the message_controller class
def index
#user = User.find(current_user.id)#414, 449, 494
#messages = #user.messages.paginate(page: params[:page])
##messages = #messages.message_pictures.paginate(page: params[:page])
end
You can see the line 4 of the index method to see the way I did mine but its not working
I believe what you need is has_many ... :through
app/models/user.rb
class User < ApplicationRecord
# ...
has_many :messages, dependent: :destroy
has_many :message_pictures, through: :messages
end
app/controllers/messages_controller.rb
class MessagesController < ApplicationController
# ...
def index
#user = User.find(current_user.id)
#messages = #user.messages.paginate(page: params[:page])
#message_pictures = #user.message_pictures
end
end
has_many ... :through simplifies the retrieving of "nested" children records via "SQL JOINS", of which normally you would have done it in a longer (more explicit way) like the following (which also works):
class MessagesController < ApplicationController
# ...
def index
#user = User.find(current_user.id)
#messages = #user.messages.paginate(page: params[:page])
#message_pictures = MessagePicture.joins(message: :user).where(
messages: { # <-- this needs to be the table name, and not the association name, and is why it is in plural form
users: { # <-- this needs to be the table name, and not the association name, and is why it is in plural form
id: #user.id
}
}
)
end
end
Update: Alternative Solution
Looking back at your question, I have a feeling you'd only want #message_pictures that corresponds to #messages and not to all#user.messages, because I noticed you have pagination for the messages. I'll do it like this instead:
app/controllers/messages_controller.rb
class MessagesController < ApplicationController
# ...
def index
#user = User.find(current_user.id)
# the `includes` here prevents N+1 SQL queries, because we are gonna loop
# through each `message_picture` in each `message` record (see index.html.erb below)
#messages = #user.messages.includes(:message_pictures).paginate(page: params[:page])
end
end
app/views/messages/index.html.erb (example)
<h1>Messages:</h1>
<% #messages.each do |message| %>
<section>
<h2>Message:</h2>
<p><%= message.content %></p>
<h3>Message Pictures:<h3>
<div>
<% message.message_pictures.each do |message_picture| %>
<% message_picture.message_pictures.each do |message_picture_attachment| %>
<%= image_tag message_picture_attachment.url.to_s %>
<% end %>
<br>
<% end %>
</div>
</section>
<% end %>
^ Above assumes MessagePicture is using carrierwave. P.S. It looks to me there's something wrong with how you defined your models, because your message has many message_pictures, while each of the message_picture also has many attached message_picture carrierwave attachments (assuming you used the "multiple-file" upload set up for carrierwave because you used mount_uploader :message_pictures, PictureUploader instead of mount_uploader :message_picture, PictureUploader. The model problem I think is because it's like this: message < message_pictures < message_pictures attachments, but (depending on your use-case), it should probably be just like message < message_pictures - message_picture attachment, or just simply message < message_pictures attachments

How can I link to the ID of a user's profile?

I have two controllers- a ProfilesController and a UsersController. I have a page full of blog posts, and I want each to have a link to the profile of the user who created them. I've been having a wee problem with this lately, and I want to start fresh, but don't know where to begin. How can I go about this?
Post controller:
def index
#shit = Static.all.order('id DESC')
if params[:search]
#posts = Post.search(params[:search]).order("created_at DESC").paginate(page: params[:page], per_page: 5)
else
#posts = Post.all.order('created_at DESC').paginate(page: params[:page], per_page: 5)
end
end
Profiles model:
class Profile < ApplicationRecord
belongs_to :user
end
Users model:
class User < ApplicationRecord
has_secure_password
validates :username, uniqueness: true
has_many :posts, foreign_key: :author
has_many :comments, foreign_key: :author
has_one :profile, foreign_key: :user
after_create :build_profile
def build_profile
Profile.create(user: self) # Associations must be defined correctly for this syntax, avoids using ID's directly.
end
end
BTW not using Devise
Use joins
def index
#shit = Static.all.order('id DESC')
scope =
if params[:search]
Post.search(params[:search])
else
Post.all
end
#posts =
scope.
joins(:users).
joins(:profiles).
order("created_at DESC").
paginate(page: params[:page], per_page: 5)
end
In single #post object you should have relation to owner (user). In your view for each post use route for user_path but provide it with #post.user
Example in view
<% #posts.each do |post| %>
<%= link_to user_path(post.user) %>
//Rest of post content
<% end %>

Add Record to Existing Collection in a Rails App

I am attempting to develop a model in which a user can add the recipe they are viewing to an existing menu of recipes they have created, similar to adding a song to a custom playlist. I believe I have the models set up correctly (using a many to many through relationship) however I am unsure how to go about the adding of the actual records to a selected collection. Any guidance would be helpful. My code is as below.
Menus Controller
class MenusController < ApplicationController
before_action :set_search
def show
#menu = Menu.find(params[:id])
end
def new
#menu = Menu.new
end
def edit
#menu = Menu.find(params[:id])
end
def create
#menu = current_user.menus.new(menu_params)
if #menu.save
redirect_to #menu
else
render 'new'
end
end
def update
#menu = Menu.find(params[:id])
if #menu.update(menu_params)
redirect_to #menu
else
render 'edit'
end
end
def destroy
#menu = Menu.find(params[:id])
#menu.destroy
redirect_to recipes_path
end
private
def menu_params
params.require(:menu).permit(:title)
end
end
Menu Model
class Menu < ApplicationRecord
belongs_to :user
has_many :menu_recipes
has_many :recipes, through: :menu_recipes
end
menu_recipe Model
class MenuRecipe < ApplicationRecord
belongs_to :menu
belongs_to :recipe
end
Recipe Model
class Recipe < ApplicationRecord
belongs_to :user
has_one_attached :cover
has_many :menu_recipes
has_many :menus, through: :menu_recipes
end
User Model
class User < ApplicationRecord
has_secure_password
has_one_attached :profile_image
has_many :recipes
has_many :menus
end
You can do something like :
def add_recipe_to_menu
menu = current_user.menus.find params[:id]
recipe = current_user.recipes.find params[:recipe_id]
menu.recipes << recipe
end
It will add a viewing recipe to existing menu of recipes.
First make sure you build the new record off the user:
class MenusController < ApplicationController
# make sure you authenticate the user first
before_action :authenticate_user!, except: [:show, :index]
def new
#menu = current_user.menus.new
end
def create
#menu = current_user.menus.new(menu_attributes)
# ...
end
end
Then we can just add a select to the form where the user can select from his recipes:
# use form_with in Rails 5.1+
<%= form_for(#menu) do |f| %>
... other fields
<div class="field">
<%= f.label :recipe_ids %>
<%= f.collection_select :recipe_ids, f.object.user.recipies, :id, :name, multiple: true %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
f.object accesses the model instance wrapped by the form builder.
recipe_ids is a special setter/getter created by ActiveRecord for has_many associations. As you may have guesses it returns an array of ids and lets the association be set with an array of ids - automatically inserting/deleting rows in the join table in the process.
You then just need to whitelist the recipe_ids param:
def menu_attributes
params.require(:menu)
.permit(:foo, :bar, recipe_ids: [])
end
recipe_ids: [] whitelists an array of permitted scalar types. Since this is a hash option it must be listed after any positional arguments to be syntactically valid.
rb(main):003:0> params.require(:menu).permit(:foo, recipe_ids: [], :bar)
SyntaxError: (irb):3: syntax error, unexpected ')', expecting =>

Use all strings in an array to search through a has_many association

Im building a search field for my profile model, the search field takes in a list of skills separated by comas and find the profiles that have those skills. The code splits up the string by comas into multiple strings in an array. Now I am trying to find profiles that have all the skills in that array but I can't seem to wrap my head around it. I've tried playing around with PSQL code in where() but I can only seem to get it to work with 1 of the skills.
I am following the advanced search guide by ryan bates.
Profile.rb
class Profile < ApplicationRecord
belongs_to :user, dependent: :destroy
has_many :educations
has_many :experiences
has_many :skills
has_many :awards
has_many :publications
def owner? user
if self.user == user
return true
else
return false
end
end
end
business_search.rb
class BusinessSearch < ApplicationRecord
def profiles
#profiles ||= find_profiles
end
private
def find_profiles
profiles = Profile.all
if self.skills.present?
profiles = profiles.joins(:skills)
skills_array(self.skills).each do |skill|
profiles = profiles.where('skills.name_en = :q or skills.name_ar = :q', q: skill)
end
end
profiles
end
def skills_array(skills)
return skills.split(/,/)
end
end
business_search_controller.rb
class BusinessSearchesController < ApplicationController
def new
#search = BusinessSearch.new
end
def create
#search = BusinessSearch.create!(search_params)
redirect_to #search
end
def show
search = BusinessSearch.find(params[:id])
#profiles = search.profiles
end
private
def search_params
params.require(:business_search).permit(:first_name, :education_field, :skills)
end
end
Adavanced search view
<h1>Advanced Search</h1>
<%= form_for #search do |f| %>
<div class="field">
<%= f.label :skills %><br />
<%= f.text_field :skills %>
</div>
<div class="actions"><%= f.submit "Search" %></div>
<% end %>
Try this query:
def find_profiles
return Profile.all if skills.blank?
Profile.joins(:skills)
.where('skills.name_en IN (:skills) OR skills.name_ar IN (:skills)', skills: skills_array(skills))
.group('profiles.id')
.having('COUNT(skills.id) = ?', skills_array(skills).size)
end
Try this one:
def find_profiles
return Profile.all if skills.blank?
Profile.joins(:skills).where('skills.name_en IN (:skills) OR skills.name_ar IN (:skills)', skills: skills_array(skills))
end

Find by linked fields Rails 5

I have a model that works like this:
class User < ApplicationRecord
has_many :deals
...
class Deal < ApplicationRecord
belongs_to :user
has_many :clients
...
class Clients < ApplicationRecord
belongs_to :deal
If I want to find all the clients listed on a certain deal I can enter Client.find_by(deal_id: x) in the console where x is the deal ID I want, but when I try to list them all on a page I'm doing something wrong.
Here's the relevant resource route
Rails.application.routes.draw do
resources :deals, only: [:show]
end
I think the problem is I'm not doing the find correctly in the controller
class DealsController < ApplicationController
def show
#deal = Deal.find_by_id(params[:id])
#client = Client.find_by(deal_id: params[:id])
end
end
On the page I'm trying to list clients like this:
<div class="client">
<% #client.each do |c| %>
<%= c.name %>
<% end %>
</div>
But the error is undefined method each' for #<Client:0x007f8e4cdecfb0>
I thought it would be pretty simple because the :id that's returned from /deal/:id is shared by both, but I'm stumped.
find_by will return only one object, each is a method defined for arrays.
So if you want clients for a particular deal, you can do
#deal = Deal.find_by_id(params[:id])
#clients = #deal.clients
or
#deal = Deal.find_by_id(params[:id])
#clients = Client.where(deal_id: params[:id])
and in view
<% #clients.each do |c| %>
<%= c.name %>
<% end %>

Resources