I have a query that adds a Customer to a certain Category. It is currently in the view template, and while it works, there's a bug - the customer is added to the category upon loading the page, before they have clicked the button.
I thought that moving that logic back to the controller might solve it (using form_for), then rendering the submit button in the view.
What do you think? And how would one implement it using form_for?
= link_to "JOIN CATEGORY NOW", root_path(#product.category.add_customer(current_customer)), class: "button4"
Edit:
Category Model
class Category < ActiveRecord::Base
#Associations
belongs_to :product
has_many :customer_categories
has_many :customers, through: :customer_categories
def add_customer(customer_id)
if customer = Customer.where(id: customer_id).first
self.customers << customer unless self.customers.include?(customer)
end
end
end
Product Model
class Product < ActiveRecord::Base
include ActionView::Helpers
#Callbacks
after_create do
Category.create product: self
end
#Associations
has_one :category, dependent: :destroy
Customer Model
class Customer < ActiveRecord::Base
#Associations
has_many :customer_categories
has_many :categories, through: :customer_categories
EDIT #2:
ActionView::Template::Error (undefined method `add_customer_category' for #<#<Class:0x007fcaebdbb5b0>:0x007fcae377ab90>):
routes:
resources :categories do
member do
get 'add_customer', to: 'categories/add_customer'
end
end
Categories Controller:
def add_customer
#product = Product.find(params[:id])
#product.category.add_customer(current_customer.id)
end
The customer is added to the category upon loading the page is happening because of this line:
= link_to "JOIN CATEGORY NOW", root_path(#product.category.add_customer(current_customer)), class: "button4"
Or more specifically this part of the code:
#product.category.add_customer(current_customer)
Since, when you load the page the Ruby code in view gets evaluated and hence the above code gets executed and customer gets added to the category.
Solution:
In your routes.rb:
resources :categories do
member do
get 'add_customer'
end
end
Now, in your view:
= link_to "JOIN CATEGORY NOW", add_customer_category_path(#product.category)
In your CategoriesController:
def add_customer
#category = Category.find(params[:id])
if #category.add_customer(current_customer)
redirect_to root_path
else
# Redirect user to an error page, maybe?
end
end
Related
How can I redirect to the next lesson that does not have userLesson (problem is lessons belongs to a course through a chapter)
Models:
class Course
has_many :lessons, through: :chapters
end
class Lesson
belongs_to :chapter
has_one :lecture, through: :chapter
end
class User
has_many :user_lessons
end
class UserLesson
#fields: user_id, lesson_id, completed(boolean)
belongs_to :user
belongs_to :lesson
end
class Chapter
has_many :lessons
belongs_to :lecture
end
here user_lessons_controller:
class UserLessonsController < ApplicationController
before_filter :set_user_and_lesson
def create
#user_lesson = UserLession.create(user_id: #user.id, lession_id: #lesson.id, completed: true)
if #user_lesson.save
# redirect_to appropriate location
else
# take the appropriate action
end
end
end
I want to redirect_to the next lesson that has not the UserLesson when saved. I have no idea how to do it as it belongs_to a chapter. Please help! Could you please help me with the query to write...
Here is the answer for your question:
Inside your user_lessons_controller:
def create
#user_lesson = UserLession.create(user_id: #user.id, lession_id: #lesson.id, completed: true)
if #user_lesson.save
#You have to determine the next_lesson object you want to redirect to
#ex : next_lessons = current_user.user_lessons.where(completed: false)
#This will return an array of active record UserLesson objects.
#depending on which next_lesson you want, you can add more conditions in `where`.
#Say you want the first element of next_lessons array. Do
##next_lesson = next_lessons.first
#after this, do:
#redirect_to #next_lesson
else
#redirect to index?? if so, add an index method in the same controller
end
end
This code will only work if you define show method in your UserLessonsController and add a show.html in your views.
Also, in config/routes.rb, add this line : resources :user_lessons.
#The various models
class Team < ActiveRecord::Base
has_many :competition_teams
has_many :competitions, through: :competition_teams
end
class Competition < ActiveRecord::Base
has_many :competition_teams
has_many :teams, through: :competition_teams
end
class CompetitionTeam < ActiveRecord::Base
belongs_to :team
belongs_to :competition
end
#The form I'm using to add teams to the competition
= semantic_form_for #competition do |f|
= f.inputs :name do
= f.input :teams, as: :select, collection: current_user.teams.select{|t| !#competition.teams.include?(t)}
= f.actions do
= f.action :submit, as: :button
#Competition update action, used to add teams
def update
#competition = Competition.find(params[:id])
teams = competition_params[:team_ids] + #competition.teams.pluck(:id)
team = Team.find(competition_params[:team_ids][1])
if team.users.pluck(:id).include?(current_user.id) && #competition.update_attribute(:team_ids, teams)
redirect_to #competition
end
end
So what I want to do is to create a button (or a link) that allows a user to remove their team from the competition. Should this be done with a custom action or a form of some sort?
I really have no idea where to go from here, so any help is very appreciated
By default, a form_for will make a post request and goes to the create action if the object is a new object, and to the update action if the object is already in the db. What you want to have it do is make a request to the delete action where you will be deleting the team from the competition. You should get the #competition_team object first.
#competition_team = CompetitionTeam.new
then
= semantic_form_for #competition_team, method: 'delete' do |f|
Then in your competition_team controller, create your destroy action along with your code to delete the team from the competition.
def destroy
#your code
end
Also, make sure to define the destroy action in your route.
I have a discussion forum where users can see a list of unread posts. The way I'm doing this is to use a Look, User and Post model:
class Look < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts, through: :looks
has_many :looks
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :looks
has_many :users, through: :looks
end
So the way this works is that there is a list of all post IDs a user has viewed. It's created through the 'show' method:
def show
if current_user
viewer = current_user
view_ids = viewer.posts.pluck(:id).uniq
not_viewed = Post.where("id not in (?)", view_ids)
not_viewed_ids = not_viewed.pluck(:id)
unless Post.find(params[:id]).in?(not_viewed_ids)
Look.create(user: current_user, post: #post, viewstamp: Time.now)
end
end
end
This all works fine so far. The problem is I want to create a Look for all posts, so that I can essentially 'mark all as read'. This line works fine for creating a Look for the current post:
unless Post.find(params[:id]).in?(not_viewed_ids)
Look.create(user: current_user, post: #post, viewstamp: Time.now)
end
...but how do I make one that creates a Look for every post? Like this:
Look.create(user: current_user, post: [NEED ARRAY OF POSTS HERE], viewstamp: Time.now)
The reason I want to do this is so a user can mark all posts as read.
You can create the Look automatically just by adding the users to the posts.
Post.all.each { |p| p.users << current_user; p.save }
I'm trying to add a 'Collections' model to group Posts so that any user can add any Post they like to any Collection they've created. The Posts will have already been created by a different user. We are just letting other users group these posts in their own Collections. Basically like bookmarking.
What is the cleanest, and most Rails-ey-way of doing this?
I've created the model and run through the migration and what not. Also I've already created proper views for Collection.
rails g model Collection title:string user_id:integer
collections_controller.rb
class CollectionsController < ApplicationController
def index
#collections = current_user.collections.all
end
def show
#collection = Collection.all
end
def new
#collection = Collection.new
end
def create
#collection = current_user.collections.build(collection_params)
if #collection.save
redirect_to #collection, notice: 'saved'
else
render action: 'new'
end
end
def update
end
private
def collection_params
params.require(:collection).permit(:title)
end
end
collection.rb
class Collection < ActiveRecord::Base
belongs_to :user
has_many :posts
validates :title, presence: true
end
post.rb
has_many :collections
It seems like has_many or has_and_belongs_to_many associations are not correct? Should I be creating another model to act as an intermediary to then use
has_many :collections :through :collectionList?
If my association is wrong, can you explain what I need to change to make this work?
Also the next part in this is since this is not being created when the Post or Collection is created, I'm not sure the best way to handle this in the view. What is the best way to handle this, keeping my view/controller as clean as possible? I just want to be able to have a button on the Post#Show page that when clicked, allows users to add that post to a Collection of their own.
In such case you should use or has_and_belongs_to_many or has_many :through association. The second one is recommended, because it allows more flexibility. So now you should:
Create new model PostsCollections
rails g model PostsCollections post_id:integer collection_id:integer
and migrate it
Set correct model associations:
Something like:
class Post < ActiveRecord::Base
has_many :posts_collections
has_many :categories, through: :posts_collections
end
class Collection < ActiveRecord::Base
has_many :posts_collections
has_many :posts, through: :posts_collections
end
class PostsCollections < ActiveRecord::Base
belongs_to :post
belongs_to :collection
end
Then you'll be able to use
#collection.first.posts << #post
And it will add #post to #collection's posts
To add a post to a collection from view
Add a new route to your routes.rb, something like:
resources :collections do # you should have this part already
post :add_post, on: :member
end
In your Collections controller add:
def add_post
#post = Post.find(params[:post_id])
#collection = Collection.find(params[:id])
#collection.posts << #post
respond_to do |format|
format.js
end
end
As for views, you'll have to create a form to show a collection select and button to add it. That form should make POST method request to add_post_collection_path(#collection) with :post_id parameter.
You can read more explanations of how rails associations work in Michael Hartl's tutorial, because that subject is very wide, and can't be explained with short answer.
In Ruby on Rails I have a user models and a jobs model joined through a different model called applicants. I have a button for the users when they want to "remove their application for this job" but I don't know how to remove the specific user, and for that matter I don't know if I'm doing a good job at adding them either (I know atleast it works).
user.rb
class User < ActiveRecord::Base
...
has_many :applicants
has_many:jobs, through: :applicants
end
job.rb
class Job < ActiveRecord::Base
...
has_many :applicants
has_many:users, through: :applicants
end
applicant.rb
class Applicant < ActiveRecord::Base
belongs_to :job
belongs_to :user
end
when someone applies for a job my jobs controller is called:
class JobsController < ApplicationController
...
def addapply
#job = Job.find(params[:id])
applicant = Applicant.find_or_initialize_by(job_id: #job.id)
applicant.update(user_id: current_user.id)
redirect_to #job
end
...
end
Does that .update indicate that whatever is there will be replaced? I'm not sure if I'm doing that right.
When someone wants to remove their application I want it to go to my jobs controller again but I'm not sure what def to make, maybe something like this?
def removeapply
#job = Job.find(params[:id])
applicant = Applicant.find_or_initialize_by(job_id: #job.id)
applicant.update(user_id: current_user.id).destroy
redirect_to #job
end
does it ave to sort through the list of user_ids save them all to an array but the one I want to remove, delete the table then put them all back in? I'm unsure how this has_many works, let alone has_many :through sorry for the ignorance!
thanks!
Let's assume the user will want to remove their own application. You can do something like this:
class UsersController < ApplicationController
def show
#applicants = current_user.applicants # or #user.find(params[:id]), whatever you prefer
end
end
class ApplicantsController < ApplicationController
def destroy
current_user.applications.find(params[:id]).destroy
redirect_to :back # or whereever
end
end
And in your view:
- #applicants.each do |applicant|
= form_for applicant, method: :delete do |f|
= f.submit
Don't forget to set a route:
resources :applicants, only: :destroy
Some observations, I would probably name the association application instead of applicant. So has_many :applications, class_name: 'Applicant'.