I have a model called "Clients". The Clients model belongs to the Users model (Users model is associated with devise). The Client can do payments to me manually (cash only). When the client does this payment, I give them access to more pages in the website. To do this, I was thinking I add a migration to the Client model like:
rails generate migration add_paid_to_clients paid:boolean
Then when the clients pay the money, I can just go to their profile and click on an option (maybe a checkbox) saying the client has paid. Only the admin(me) can see this part. The way I would implement that is, in the user profile view:
<% if current_user.user_type == :client%>
userinformation....blablabla
<% elsif current_user.user_type == :admin %>
userinformation....blablabla
AND the checkbox I talked about.
<% end %>
The user_type is a predefined function to figure out if the current_user is admin or just a client. I have done this part.
So in the private pages(the pages you can get access to after paying), I can have some logic like If current_user.paid? THEN show them this page.
How can I implement the checkbox part? After the migration(if there is a better way,please let me know),how can I just "flip the switch" to let them get more access?
My Clients contoller:
class ClientController < ApplicationController
before_action :find_client, only: [:show, :edit, :update, :destroy]
def index
end
def show
end
def new
#client = current_user.build_client
end
def create
#client = current_user.build_client(client_params)
if #client.save
redirect_to clients_path
else
render 'new'
end
end
def edit
end
def update
end
def destroy
#client.destroy
redirect_to root_path
end
private
def client_params
params.require(:client).permit(:name, :company, :position, :number, :email, :client_img)
end
def find_client
#client = Client.find(params[:id])
end
end
This is not real cash, this is a project for school.
Something like this?
In the form for the admin:
<%= form_for #user do |f| %>
<%= check_box_tag 'paid', 'yes', true %>
<%= f.submit "Update" %>
<% end %>
Then in the controller:
class ClientController < ApplicationController
before_action :find_client, only: [:show, :edit, :update, :destroy]
def index
end
def show
end
def new
#client = current_user.build_client
end
def create
#client = current_user.build_client(client_params)
if #client.save
redirect_to clients_path
else
render 'new'
end
end
def edit
end
def update
end
def destroy
#client.destroy
redirect_to root_path
end
private
def client_params
if current_user.user_type == :admin
# This is different because only admin can update paid
# To make a string a boolean
params[:client][:paid] = params[:client][:paid] == 'yes'
params.require(:client).permit(:paid, :name, :company, :position, :number, :email, :client_img)
else
params.require(:client).permit(:name, :company, :position, :number, :email, :client_img)
end
end
def find_client
#client = Client.find(params[:id])
end
end
you can do this more properly using the protected attributes, described here
After that you want to make a PaidController
class PaidController < ApplicationController
before_filter :authorize_paid
def authorize_paid
return head(:forbidden) unless current_user.paid?
end
end
Then make sure that all the protected pages inherit from the PaidController
Related
I'm new at rails so bear with me pls. My problem is so specific. I'm creating a User blog, where they could put any posts. So Users has a blogs, and blogs has posts. So when user create a blog, all posts in his blog should be written by him. Other users can't write not on their blogs.
post_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!
before_action :authorize_user!, only: [:edit, :update, :destroy]
expose :blog
expose :post
def show
end
def new
end
def edit
end
def create
post.user = current_user
post.save
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end
def update
post.update(post_params)
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end
def destroy
post.destroy
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end
private
def authorize_user!
authorize(post, :authorized?)
end
def post_params
params.require(:post).permit(:title, :content, :user_id, :blog_id)
end
end
Here i'm using pundit to authorize user, when they update or destroy posts (users can update or destroy only their own posts) and it works perfectly.
views/posts/new
.row
.columns
h2 = title("New post")
.row
.medium-5.columns
= simple_form_for post do |f|
= f.error_notification
.form-inputs
= f.input :title
= f.input :content
= f.hidden_field :blog_id, value: blog.id
.form-actions
= f.button :submit
Here i'm using the hidden form to set the blog_id which I take from params. Http link looks like http://localhost:3000/posts/new?blog_id=6. The problem is that each user can copy this link to create the post( and they are not the blog owners).
post_policy.rb
class PostPolicy < ApplicationPolicy
def authorized?
record.user == user
end
end
How should I check the blog's owner before post creating? Maybe I have a wrong way to create posts like this(using hidden form).
Link to create new post
= link_to 'New Post', new_post_path(blog_id: blog.id)
I hope, it will work for you
application_controller.rb
class ApplicationController
include Pundit
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
before_action :authenticate_admin_user!
helper_method :current_user
def pundit_user
current_admin_user
end
def current_user
#current_user ||= User.find(current_admin_user.id)
end
end
posts_controller.rb
class PostsController < ApplicationController
before_action :set_blog
def new
authorize(Post)
end
def edit
#post = #blog.posts.find(params[:id])
authorize(#post)
end
def index
#posts = policy_scope(#blog.posts)
end
private
def set_blog
#blog = current_user.blogs.find(params[:blog_id])
end
end
post_policy.rb
class PostPolicy < ApplicationPolicy
def show?
true
end
def index?
true
end
def new?
create?
end
def create?
true
end
def edit?
update?
end
def update?
scope_include_object?
end
def destroy?
scope_include_object?
end
class Scope < Scope
def resolve
scope.joins(:blog).where(blogs: { admin_user_id: user.id })
end
end
def scope_include_object?
scope.where(id: record.id).exists?
end
end
routes.rb
Rails.application.routes.draw do
devise_for :admin_users
resources :blogs do
resources :posts
end
end
I have 2 models:
User model and Profile Model.
My association is as follows:
Class User < ActiveRecord::Base
has_one :profile
end
Class Profile < ActiveRecord::Base
belongs_to :user
validates user_id, presence: true
end
My Controllers is as follows:
USER:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
if logged_in?
#micropost = current_user.microposts.build
#profile = current_user.build_profile
#new_comment = Comment.build_from(#user, current_user.id, ' ')
end
#microposts = #user.microposts.paginate(page: params[:page])
#profile_player = #user.profile
end
end
PROFILE:
class ProfilesController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: [:destroy]
def create
#profile = current_user.build_profile(profile_params)
if #profile.save
flash[:success] = 'Profile Has Been Published'
# redirect_to request.referrer || root_url
redirect_to users_url
else
render :'pages/home'
end
end
def update
#profile.update(profile_params)
redirect_to user_url
end
def destroy
end
private
def profile_params
params.require(:profile).permit(:name, :age, :nationality, :country, :city, :height, :weight,
:dominant_hand, :play_position, :highschool, :college, :team,
:awards, :highlights)
end
def correct_user
#profile = current_user.profile.find_by(id: params[:id])
redirect_to root_url if #profile.nil?
end
end
Now, what I'm trying to do is render the profile view partial in the user show page (following Michael Hartl's tutorial):
hence I'm rendering the view via the instance variable I created in Users Controller show action for profile:
def show
##username = params[:id] ==============> this is intended to show the user name instead
# of the user id in the address bar
#user = User.find(params[:id])
if logged_in?
#micropost = current_user.microposts.build
#profile = current_user.build_profile
#new_comment = Comment.build_from(#user, current_user.id, ' ')
end
#microposts = #user.microposts.paginate(page: params[:page])
#profile_player = #user.profile
end
so, in my user show page:
I'm rendering the profile view like this:
Now here is the error I run into, my profile saves correctly when the form is submitted, however, when I return to the User show page (I'm rendering the profile form in the user home page) to view the profile, I get the error:
'nil' is not an ActiveModel-compatible object. It must implement :to_partial_path.
<div class="current-user-persona">
<%= render #profile_player %> =====> highlighted section for error
</div>
I'm not sure what I'm doing wrong here, can you help me?
been staring at this for days.
This has been asked on SO a lot before, but I can't find anything that quite applies. What I'm trying to do is render an edit form for SettingsController in the edit view of UsersController. I'm super new to RoR, so I'm not even sure what I'm doing wrong.
This questions seems closest, but when I initialize #setting = Setting.new in the Users controller, I get a Settings form without the defaults set for new users in the migration. But if I initialize #setting = Setting.edit or Setting.update, I get an undefined method or wrong number of arguments error.
When the Setting.new form is saved, it throws this error:
undefined method for find_by_id in the SettingsController: app/controllers/settings_controller.rb:43:in `correct_user'.
When I check the database, the settings records are being correctly created when a user is created, but the settings record is not updated when the form is saved.
setting.rb:
class Setting < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
end
user.rb:
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
has_one :setting, dependent: :destroy
after_create :create_setting
end
UsersController:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update, :index, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
#setting = Setting.update
end
def update
#user = User.find(params[:id])
#setting = Setting.update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated!"
redirect_to #user
else
render 'edit'
end
end
def settings
#title = "Settings"
#setting = Setting.find_by_user_id(params[:user_id])
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
the SettingsController:
class SettingsController < ApplicationController
before_action :logged_in_user, only: [:create, :edit, :update, :show, :index]
before_action :correct_user, only: [:create, :edit, :update, :show, :index]
def index
#settings = Setting
end
def show
#setting = User.find(params[:id]).setting
end
def new
#setting = Setting.new
end
def edit
#setting = Setting.find(params[:id])
end
def create
#setting = current_user.settings.build(setting_params)
#setting.save
end
def update
#setting = Setting.find(params[:id])
if #setting.update_attributes(post_params)
flash[:success] = "Settings updated!"
redirect_to request.referrer
else
render 'edit'
end
end
private
def setting_params
params.require(:setting).permit(:reading_theme)
end
def correct_user
#setting = current_user.setting.find_by_id(params[:id]) ##the line which throws an error when the form is saved
redirect_to root_url if #setting.nil?
end
end
The form partial:
<%= form_for(#setting) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= radio_button_tag(:reading_theme, "flatly") %>
<%= label_tag(:reading_theme_flatly, "Light (Default)") %>
</div>
<div class="field">
<%= radio_button_tag(:reading_theme, "darkly") %>
<%= label_tag(:reading_theme_darkly, "Dark") %>
</div>
<%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>
routes.rb:
resources :users do
member do
get :following, :followers
end
end
resources :settings, only: [:new, :create, :edit, :update]
...
ETA: the settings migration:
class CreateSettings < ActiveRecord::Migration
def change
create_table :settings do |t|
t.string :reading_theme, default: => "flatly"
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
How do I get the proper defaults so that my form can be saved correctly?
Any defaults that you include for fields in the migration will be "unknown" to the model class (Setting) in Ruby. Ruby (or rather Rails ActiveRecord) does not read the default values from the table definition when creating a model object. This can lead to a dual personality problem like you're seeing here.
What you have to do is to add the relevant defaults into the Ruby code, where appropriate. For example, in your Settings controller, you can make these changes:
def new
#setting = Setting.new
# Set any defaults that will be visible to the user on the form
#setting.reading_theme = "flatly"
# The form will allow the user to choose their own values, based on the defaults
end
def create
#setting = current_user.settings.build(setting_params)
# Set any defaults that will NOT be visible to the user
#setting.save
end
This gives you the ability to distinguish between default values that are visible to the user and defaults that are not.
Note that you also have the option of establishing defaults when you create the model object, but this may be more complicated in some situations, and seems to be far less common in practical use. There's an SO answer for that in How to initialize an ActiveRecord with values in Rails?, in case this better suits your needs.
Not can use find_by_id in has_one relationship
#setting = current_user.setting.find_by_id(params[:id])
Just #setting = current_user.setting
I want to the category I create in relation with the post I want to create. I don't want to add a gem or anything else I think we can do it with has_manyand belongs_to
I create two tables Posts and Category and I want to choose a category in a collection and this is written in the post new#view I want to create and on the post show#view and post index#view.
Models for posts is :
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :category
TAGS = ["Design", "Mode", "Tendance", "Life-Style", "Tradition", "Gastronomie", "Insolite", "Technologie"]
validates :tag, inclusion: { in: Post::TAGS, allow_nil: false }
mount_uploader :cover, ImageUploader
end
and categories is foreign keys for posts here is the model
class Category < ActiveRecord::Base
has_many :posts
NAMES = ["JAPON", "CHINE", "INDE"]
validates :name, inclusion: { in: Category::NAMES, allow_nil: false }
end
Posts Controllers are here
class PostsController < ApplicationController
before_filter :authenticate_user!, except: [:index, :show]
before_action :find_post, only: [:show, :edit, :update, :destroy]
def index
#posts = Post.all
end
def show
# #alert_message = "Vous lisez #{#post.title}"
end
def new
# if current_user and current_user.admin?
#post = Post.new
# else
# redirect_to posts_path
# end
end
def create
# if current_user and current_user.admin?
#post = current_user.posts.new(post_params)
##post = current_user.posts.new(post_params)
if #post.save
redirect_to #post
else
render :new
end
# else
# render 'shared/404.html.erb'
# end
end
def edit
end
def update
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
def destroy
#post.destroy
redirect_to :back
end
private
def find_post
#post = Post.find(params[:id])
end
# def set_category
# #post_category = Category.find(params[:category_id])
# end
def post_params
params.require(:post).permit(:title, :subtitle, :introduction, :body, :cover, :tag, :category_id)
end
end
and categories_controller are here
class CategoriesController < ApplicationController
before_action :set_category, only: [:show, :new, :create, :destroy]
def show
#category = Category.find(params[:id])
end
def index
#categories = Category.all
end
def create
#category = Category.new(category_params)
if #category.save
redirect_to #post
else
render :new
end
end
def new
#category = Category.new
end
def edit
end
def update
end
def destroy
#category = Category.find(params[:id])
#category.destroy
redirect_to post_path(#post)
end
private
# def set_post
# #post = Post.find(params[:post_id])
# end
# def set_category
# #category = Category.find(params[:category_id])
# end
def set_category
if params[:id].present?
#category = Category.find(params[:id])
else
#category = Category.new
end
end
# def find_category
# #category = Category.find(params[:id])
# end
def category_params
params.require(:category).permit(:name, :description)
end
end
Please could you show the right way to add a category I choose in collection and I show in post new#view show#view index#view.
Thank you for your help.
If a category can belong to more than one post, I would recommend using a has_may_through relationship for your data model.
class Post
has_many :post_categories
has_many :categories, through: post_categories
accepts_nested_attributes_for :categories
end
class Category
has_many :post_categories
has_many :posts, through: post_categories
end
class PostCategory
belongs_to :posts
belongs_to :categories
end
You will need to create a migration to add the 'through' table, PostCategory, which will consist of a post_id and a category_id.
In the controller
def new
#post = Post.new
#post.categories.build
end
def post_params
params.require(:post).permit(:title, :subtitle, :introduction, :body, :cover, :tag, category_ids: [])
end
In your form, you can use fields_for to build the form for categories.
If you set this all up, rails will handle the creation of the category when the post is created. Then you will be able to call category.posts to get all the posts with that category and you can call post.categories to get all the categories assigned to the post.
Assigning a Post to a Category can be accomplished in the create method of your PostsController. You are already passing the category_id to the controller via the params.
def create
#post = current_user.posts.new(post_params)
#category = Category.find(params[:category_id])
if #post.save && (#category.posts << #post)
redirect_to #post
else
render :new
end
end
When you are trying to show a post in a view, you should be able to access that category directly.
<%= post.category.name %>
If you allow some posts to be created without a category, you can simply not show anything, or show a "No Category" message.
# Don't show anything if the post doesn't belong to a category
<%= post.category.name if post.category.present? %>
# Show a "No Categories" message (this uses the ternary operator)
<%= post.category.present? ? post.category.name : "No Category" %>
I am trying to follow this video railscast #364 but I am having a lot of trouble with my nested routes. When I use this code:
<%= link_to "up", vote_movie_review_path(#movie, #reviews, type: "up"), method: "post" %>
I get this error when I select up vote:
ActiveRecord::RecordNotFound in ReviewsController#vote
Couldn't find Review with 'id'=# <Review::ActiveRecord_Relation:0x007f0358c1e550>
This is my route:
vote_movie_review POST /movies/:movie_id/reviews/:id/vote(.:format) genre_linkers#vote
I created another model that was not nested using this code:
<%= link_to "up", vote_movie_path(movie, type: "up"), method: "post" %>
and that one worked. So I am thinking it has to be something wrong with my path or how I am calling the objects. I have spent almost all day working on this, I really need help.
review_controller.rb
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
before_action :set_movie
before_action :authenticate_user!
respond_to :html
def index
#reviews = Review.all
respond_with(#reviews)
end
def show
end
def vote
value = params[:type] == "up" ? 1 : -1
#review = Review.find(params[:id])
#review.add_evaluation(:vote, value, current_user)
redirect_to :back, notice: "thanks for the vote"
end
def new
#review = Review.new
respond_with(#review)
end
def edit
end
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
#review.movie_id = #movie.id
if #review.save
redirect_to #movie
else
render 'new'
end
end
def update
#review.update(review_params)
respond_with(#review)
end
def destroy
#review.destroy
respond_with(#review)
end
private
def set_review
#review = Review.find(params[:id])
end
def set_movie
#movie = Movie.find(params[:movie_id])
end
def review_params
params.require(:review).permit(:genre, :description, :vote)
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users
resources :movies do
resources :reviews do
member { post :vote }
end
end
root 'movies#index'
end
and the model
review.rb
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :movie
has_reputation :votes, source: :user, aggregated_by: :sum
end
The culprit is this part of your link_to: vote_movie_review_path(#movie, #reviews, type: "up"). #reviews is an ActiveRecord::Relation and not a Review record, hence no record with an ID can be found.