Having trouble adding view created from a has_many: through table. - ruby-on-rails

Hopefully I get all the terminology right here - if not please forgive, still very new.
I am working on a course project #Bloc and am having trouble with adding a list of collaborators to my index view for a different controller.
What I am trying to achieve is:
Show all user wikis - working.
Show all wikis where user is listed as a collaborator (link created through a join table and has_many through reference.)
My index view looks like: (/wikis/index)
<h1>My Wikis</h1>
<% if policy(Wiki.new).create? %>
<%= link_to "New Wiki", new_wiki_path, class: 'btn btn-success' %>
<% end %>
<% #wikis.each do |wikis| %>
<div class="media">
<div class="media-body">
<h4 class="media-heading">
<%= link_to wikis.title, wikis %>
</h4>
<small>
Created <%= time_ago_in_words(wikis.created_at) %> ago by <%= wikis.user.name %><br/>
</small>
</div>
</div>
<% end %>
<h1>My Collaborations</h1>
<% #collaborations.each do |collaborations| %>
<div class="media">
<div class="media-body">
<h4 class="media-heading">
<%= link_to collaborations.title, collaborations %>
</h4>
</div>
</div>
<% end %>
<%= will_paginate #wikis %>
My Wikis controller looks like: (shortened)
class WikisController < ApplicationController
respond_to :html, :js
def index
# #user = current_user
#wikis = current_user.wikis.paginate(page: params[:page], per_page: 10)
##collaboration = #wiki.collaboration.build(collaboration_params)
#collaborations = #collaboration.where(:user_id == current_user)
authorize #wikis
end
def show
#wikis = Wiki.friendly.find(params[:id])
if request.path != wiki_path(#wikis)
redirect_to #wikis, status: :moved_permanently
authorize #wikis
end
end
end
private
def collaboration_params
params.require(:collaboration).permit(:user_id, :wiki_id)
end
end
From the Rails Console - I can see that my collaboration relationship is working as expected - I just cannot quite figure out what the syntax is I should be using to make it render on my index view.
2.0.0-p481 :022 > Collaboration.last
Collaboration Load (1.9ms) SELECT "collaborations".* FROM "collaborations" ORDER BY "collaborations"."id" DESC LIMIT 1
log writing failed. Protocol error - /home/vagrant/code/blocipedia/log/development.log
=> #<Collaboration id: 3, user_id: 1, wiki_id: 16>
2.0.0-p481 :023 >
Any pointers would be greatly appreciated. I've tried what feels like about 100 different combinations to figure this out.
Right now the error I trigger on index view is:
undefined method `where' for nil:NilClass
Cheers
Kevin.

Object
The simple error you have is here:
#collaborations = #collaboration.where(:user_id == current_user)
The error here is that you're calling .where on a variable which has not been populated (defined).
Ruby / Rails works somewhat differently to other languages, in that it's object orientated. This means that each time Rails calls an "object", it will actually be defined, just not populated.
As a rule of thumb, the no method error is generally (although not always) a result of the system having not populated the variable correctly.
Model
The way to fix this is very simple -
You just need to be able to declare the #collaboration object before calling the .where method: on it. However, as you're using the .build method to define this object, you're not going to get anywhere.
Essentially, when you perform a .where lookup, you'll be using ActiveRecord to ping the datatables in the backend, populating an object with the various attributes from the Model (look up MVC programming pattern for more about this)
What you need to do (if you want to call .where) is create an instance of the model you wish to query inside the database. To do this, you either have to call the model directly, or through an associative object:
def index
#wikis = current_user.wikis.paginate(page: params[:page], per_page: 10)
#collaborations = Collaboration.where(user_id: current_user.id)
authorize #wikis
end
System
As you're new to Rails, let me give you some details on how to make the system work properly.
Firstly, you need to appreciate the role of ActiveRecord Associations - these are built on the ActiveRecord pattern, making it much simpler to build "objects" around database-driven data
In short, if you associate two or more objects with ActiveRecord, it means you can call the associative data through the primary object:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :collaborations
end
#app/models/collaboration.rb
Class Collaboration < ActiveRecord::Base
belongs_to :user
end
This means that instead of calling #collaborations = Collaboration.where(user_id: current_user.id), you'll simply be able to call#collaborations = current_user.collaborations`
Fix
The fix should be as follows:
#app/views/wiki/index.html.erb
...
<% current_user.collaborations.each do |collaboration| %>
<div class="media">
<div class="media-body">
<h4 class="media-heading">
<%= link_to collaboration.title, collaboration %>
</h4>
</div>
</div>
<% end %>
#app/controllers/wikis_controller.rb
Class WikisController < ApplicationController
def index
#wikis = current_user.wikis.paginate(page: params[:page], per_page: 10)
authorize #wikis
end
end

Related

has_scope filter by cost feature [Ruby]

I need to set up a feature where the index page of freelancers can be filtered to only display based on a range on the attribute of price. This needs to be done from the user side, i was thinking two input boxes and a submit button.
How do I go about doing this? I checked out the has_scope gem, but I'm worried it's going to mess up my already defined order of freelancers, where they are ordered by the attribute featured (boolean). Essentially the order of the freelancers must stay the same, but then a filter must be added to that ordered object, to only show the freelancers in that price range.
There must be a way to do this but I'm very unsure. I'm going to do a bit of a code dump below, can someone point me in the right direction and show me how?
class HomeController < ApplicationController
before_action :set_freelancer, only: %i[ show edit update destroy ]
def index
#pagy, #freelancers = pagy(Freelancer.order(featured: :desc))
end
private
# Use callbacks to share common setup or constraints between actions.
def set_freelancer
#freelancer = Freelancer.find(params[:id])
end
end
My home controller
<% if user_signed_in?%>
<%== pagy_nav(#pagy)%>
<div class="d-flex flex-wrap justify-content flex-shrink-1 gap-3">
<%#freelancers.each do |freelancer| %>
<div class="card mb-3" style="max-width: 540px">
<div class="row g-0">
<div class="col-md-4">
<img src="https://i.pinimg.com/originals/57/f5/da/57f5da08bd8f52bc2d3e4ebadd67b642.jpg" class="img-fluid rounded-start" alt="6-logo">
</div>
<div class="col-md-8">
<div class="card-body">
<%= render freelancer %>
<%= link_to "Show this freelancer", freelancer, class: "btn btn-info" %>
</div>
</div>
</div>
<br/>
</div>
<%end%>
<% else %>
<h1>Please sign up, or sign in!</h1>
<%end%>
My webpage outputting each freelancer, ive also added my github itself below, im aware ive asked alot for help lately here but i am learning alot so i appreciate it
https://github.com/LCzoboriek/freelancer-assignment
(UPDATE)
So ive now added this code below to my home_controller.rb
def index
freelancers_scope = Freelancer.order(featured: :desc)
freelancers_scope = freelancers_scope.where("cost >= ?", params[:cost_lower_than]) if params[:cost_greater_than].present?
freelancers_scope = freelancers_scope.where("cost <= ?", params[:cost_greater_than]) if params[:cost_lower_than].present?
#pagy, #freelancers = pagy(freelancers_scope)
end
and this to my views, but its throwing an error saying undefined local variable or method `index' for #ActionView::Base:0x00000000013178. How do i point my form to that freelancers_scope part in my controller?
<%= form_for(index) do |f| %>
<%= f.input :cost_greater_than%>
<%= f.input :cost_lower_than%>
<%= submit_tag("Submit") %>
<% end %>
i was thinking two input boxs and a submit button.
this sounds absolutely fine
this is a very basic thing and you don't really need a gem for that, how about:
def index
freelancers_scope = Freelancer.order(featured: :desc)
freelancers_scope = freelancers_scope.where("cost >= ?", params[:cost_greater_than]) if params[:cost_greater_than].present?
freelancers_scope = freelancers_scope.where("cost <= ?", params[:cost_greater_than]) if params[:cost_lower_than].present?
#pagy, #freelancers = pagy(freelancers_scope)
end
if you really want a scope you could later define it in the Freelancer model so you wouldn't have to call where directly in the controller

Rails nested form not working (child parameters not passed at all)

Model
`Buyer has_many :orders`
`Buyer accepts_nested_attributes_for :order`
`Order belongs_to :buyer`
View (buyers#new)
<%= form_for #buyer do |f| %>
<%= f.fields_for :orders do |o| %>
<div class="row">
<div class="col-xs-12">
<%= o.label "Select your meal" %>
</div>
</div>
<div class="row section">
<div class="col-xs-1"></div>
<% ["Pasta, pesto, & pea greens (veggie)",
"Mushroom cutlets & mornay sauce (veggie)",
"Italian breaded pork chop",
"Chicken kabobs with tzatziki",
"Asian-style sweet & sour beef"].each do |m| %>
<div class="col-xs-2 zero-padding">
<div class="col-xs-12">
<% image_tag "#{m}.jpg" %>
</div>
<div class="col-xs-12 text-center">
<%= o.radio_button :meal, m %>
<br>
<%= m %>
</div>
</div>
<% end %>
<div class="clearfix">
</div>
<% end %>
...
<% end %>
Controller (buyers)
def new
#buyer = Buyer.new
#buyer.orders.build
end
def create
# just to illustrate what i'm talking about, even the unsanitized parameters do not have orders_attributes
puts params
=> {"utf8"=>"✓", "buyer"=>{"first_stripe_token"=>"tok_16zExiKQ2oHmpkBLo9y45Cv3", "delivery_instructions"=>"random", "zipcode"=>"02110", "email"=>"test#example.com", "phone1"=>"123", "phone2"=>"456", "phone3"=>"0789", "agree_tos"=>"1"}, "controller"=>"buyers", "action"=>"create"}
end
def buyer_params
params.require(:buyer).permit(:first_stripe_token, :phone1, :phone2, :phone3, :zipcode, :delivery_instructions, :agree_tos, :email, orders_attributes: [:meal] )
end
Routes
match '/ondemand/create', to: 'buyers#create', via: :post, as: "buyers"
Some folks have asked questions about how to permit nested attributes via strong parameters. That's not my challenge. In my case, the nested attributes somehow are completely, as you can see above where I do a puts on the unsanitized parameters.
help!
If your f.fields_for block was correct and there was data in it, then the params would be correct even if strong params decides to block them. The fact that they don't show up in the params but other fields do show in the params makes me feel like the problem is in your f.fields_for block.
Figured out the answer... but a little worried since this seems like such a common problem and I haven't seen a mention of it anywhere. So if I'm doing something totally wrong... please clue me in.
What happened is that the orders_attributes were not being passed on the SECOND try of a failed submission (I'm running rspec tests here). And the reason they weren't being passed is because they were nonexistent on the form. Here's why:
def new
#buyer = Buyer.new
#buyer.orders.build
end
In the new action, the form creates fields for both buyer and order because both have been initialized. But my original create action looked like this:
def create
#buyer = Buyer.new(buyer_params)
...
if #buyer.save
redirect_to '/'
else
render 'new'
end
end
In other words, if everything went well, and the buyer_params built out valid buyer and order, great! But if some kind of error happened, and the page re-rendered, there is no order object to build the form fields for!
As a result, the answer:
def create
#buyer = Buyer.new(buyer_params)
#buyer.orders.first_or_intialize
if #buyer.save
...
end
The result is this:
If there are no errors the first line creates both buyer and order and the second line is kind of a moot point since it will just call the just-created order (in my case even though I've written it as a has_many, realistically a buyer only has one order; even if that weren't the case, I'm not using the order object to do anything, so the second line does no harm)
If there are errors then the second line instantiates an order object so that when the page is re-rendered, there is an order object to build fields for

Interpolating data in a rails view

I'm a beginner at rails and thus far interplating data in views has been pretty straight forward. I've been introduced to something slightly new as far as how the controllers are setup and as a result I'm not sure how one would go about getting the data to present in the view.
First controller
class PagesController < ApplicationController
def index
#guestbook_entry = GuestbookEntry.new
render "welcome"
end
end
Second controller
class GuestbookEntriesController < ApplicationController
def create
GuestbookEntry.create(guestbook_entry_params)
redirect_to root_path, notice: "Thank you for your entry."
end
private
def guestbook_entry_params
params.require(:guestbook_entry).permit(:body)
end
end
And here is the welcome.html.erb
<h1>Welcome to My Guestbook</h1>
<br>
<%= image_tag("under_construction.gif") %>
<div id="guestbook-entries">
<p>Guestbook Entries:</p>
<ul>
</ul>
</div>
<%= form_for #guestbook_entry do |f| %>
<%= f.label :body, "Guestbook Entry:" %>
<%= f.text_area :body %>
<%= f.submit "Submit" %>
<% end %>
So it wants me to iterate through all the entries and display them on a welcome page that's located in view/pages/welcome.html.erb.
Up to this point I guess I've only been doing basic simple rails applications where the view corresponded with the controller, and followed the typical CRUD setup, where index would hold the #xxx = Xxxx.all and new/create would handle #xxx = Xxxx.new/create/build. I thought I could simply move the PageController's index action to create/new and do
def index
#guestbook_entry = GuestbookEntry.all
render "welcome"
end
To satisfy the test (it looks for render welcome in the index action)
This seems weird but again I admit, I'm a beginner.
If you want to list all the guest book entries on your root page you would do something like:
class PagesController < ApplicationController
def index
#guestbook_entry = GuestbookEntry.new
#guestbook_entries = GuestbookEntry.limit(10).all
render "welcome"
end
end
And in your view you would list them like:
<% if #guestbook_entries.any? %>
<div id="guestbook-entries">
<p>Guestbook Entries:</p>
<% #guestbook_entries.each do |entry| %>
<ul>
<li class="entry"><%= h(entry.body) %></li>
</ul>
<% end %>
</div>
<% end %>
The rest of you application is correct - you should be creating entries in GuestbookEntriesController#create. In many real life applications then the functionality of the standard new and edit actions can actually be a totally different controller.

Devise - how to show user's post

I use Devise gem for authentication.
In database I have users table and posts table in my database schema (and Post controller).
In post controller I want to find all posts assigned to specific user. I have user_id in posts table.
How to get all user's posts or how to check if specific post is assigned for SIGNED IN user.
I thought about something like this (of course is only pseudocode:
current_user.id == Post.where(params:[post_id]).user_id
So how to get current user id in Devise and how to check the current user id is the same like eg. user_id assigned to viewing post (I want to add 'edit' function when current user is post owner) and how to find all post which current user is owner.
Associations
Firstly, your user_id column in your posts table is what's known as a foreign_key
Foreign keys are used in relational database systems to give you the ability to call associative data from a single record. Simply, it means that you'll be able to use the ActiveRecord associations to call the data you require, rather than having to call it individually:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :posts
end
#app/models/post.rb
class Post < ActiveRecord::Base
belongs_to :user
end
This will give you the ability to use the following call:
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def index
#posts = current_user.posts
end
end
You'll be best served looking up the has_many association:
Fix
In regards to showing your posts for your users, you need to be sure that you have the correct "flow" set up. What I mean is you need some condition to know whether your user is signed in & that #posts is set:
#app/views/posts/index.html.erb
<% if #posts.present? %>
<% #posts.each do |post| %>
<%= post.title %>
<% end %>
<% end %>
Maybe this is the first time you use Devise. You can access current_user inside controllers or views. I imagine you could do something like this
In controller (posts_controller.rb):
#posts = current_user.posts
In view (posts/show.html.erb, I guess):
if current_user.id = #post.current_user
#render something here
end
Get all post which current user is owner.
#posts = Post.where(:user_id => current_user.id)
and on your view
<%-# commented : checking if #posts is empty -%>
<% if #posts.empty? %>
<span>Sorry, post is empty </span>
<% else %>
<%= #posts.each do |p| %>
<% if p.user_id == current_user.id %>
<% link_to "edit", edit_path(p) %>
<% end %>
<% end %>
<% end %>
There are many ways you could get current_user posts. I'll go the long way.
we need
an action
an action view and a partial
a route
a link_to
* action *
def my_posts
#posts = current_user.posts.all.order(created_at: 'DESC')
end
* view *
my_posts.html.erb
<% if #posts.present? %>
<%= render 'posts' posts: #posts %>
<% else %>
<h1>You don't have any posts yet! create one</h1>
<% end %>
_posts.html.erb
<%posts.each do |post| %>
<%= post.title %>
<% end %>
index.html.erb
<%= render 'posts' posts: #posts %>
route
get 'post' => 'posts#my_posts', as: :my_posts
link_to
<%= link_to 'My posts', my_posts_path %>
I may be late but someone can find it useful :)

Adding a user_id to a Message model

I currently have a simple app that includes user authentication through devise and a message model(the message model uses Jquery and Faye). Both are working fine independently, but I would like to add a user_id to the messages.
I have already updated the schema and models with the relationship, but I am having trouble inputting the necessary code to have the view and controller input the relationship into the db, while keeping the jquery working. I wasn't sure of the best way, but here I tried to create a hidden field that would pull the user_id, not sure if this is even possible. Here is the applicable code, any help is appreciated.
Message index.html.erb
<ul id="chat">
<%= render #messages %>
</ul>
<%= form_for Message.new, :remote => true do |f| %>
<div class="field">
<%= f.text_field :content %>
</div>
<div class="field">
<%= f.hidden_field :user_id %>
</div>
<div class="actions">
<%= f.submit "Send" %>
</div>
<% end %>
create.js.erb for messages
<% broadcast "/messages" do %>
$("#chat").append("<%= escape_javascript render(#user.message) %>");
<% end %>
$("#new_message")[0].reset();
Messages Controller
class MessagesController < ApplicationController
def index
if #authentications = current_user
#messages = Message.all
else
redirect_to authentications_url
flash[:notice] = "You need to sign in before answering questions"
end
end
def create
#user = User.find(params[:user_id])
#message = #user.message.create(params[:message])
end
end
I think you have everything you would need, but if not, let me know and I will be happy to provide it for you.
Thanks, everyone.
two things to correct,
1)use user association to create message instance in form(probably current_user if logged-in user create a message)
<%= form_for user.messages.new, :remote => true do |f| %> #// assuming its has many association
2) if it is has_many association then change association in create action
#message = #user.messages.create(params[:message])

Resources