save current_user.id into another column - ruby-on-rails

currently I have the model commodity_group, there are created_by, and updated_by columns in it. It belongs to user, and one user will have commodity_groups. Now, I want to whenever a user created a commodity group, his id will be saved into the created_by, and whenever somebody update a commodity group, his id will be saved into the field update_by.
At the moment, i get this error:
unknown attribute 'user_id' for CommodityGroup.
basically, I don't want to add the column user_id to the commodity_group table since it is the same with the column created_by. Therefore, could somebody guide me here a little bit. Here are my files:
commodity_groups_controller.rb:
class CommodityGroupsController < ApplicationController
before_action :set_commodity_group, only: [:show, :edit, :update, :destroy]
# GET /commodity_groups
def index
#commodity_groups = CommodityGroup.all
end
# GET /commodity_groups/1
def show
end
# GET /commodity_groups/new
def new
#commodity_group = current_user.commodity_groups.build
end
# GET /commodity_groups/1/edit
def edit
end
# POST /commodity_groups
def create
#commodity_group = current_user.commodity_groups.build(commodity_group_params)
if #commodity_group.save
redirect_to commodity_groups_path, notice: init_message(:success, t('message.new_success', page_name: t('page_name.commodity_group')))
else
redirect_to new_commodity_group_path, notice: init_message(:error, t('message.new_error', page_name: t('page_name.commodity_group')))
end
end
# PATCH/PUT /commodity_groups/1
def update
if #commodity_group.update(commodity_group_params)
redirect_to #commodity_group, notice: 'Commodity group was successfully updated.'
else
render :edit
end
end
# DELETE /commodity_groups/1
def destroy
#commodity_group.destroy
redirect_to commodity_groups_url, notice: 'Commodity group was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_commodity_group
#commodity_group = CommodityGroup.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def commodity_group_params
params[:commodity_group]
end
end
commodity_group.rb:
class CommodityGroup < ActiveRecord::Base
extend FriendlyId
friendly_id :code, use: :history
belongs_to :user_created,
class_name: 'User',
primary_key: :id,
foreign_key: :created_by
belongs_to :user_updated,
class_name: 'User',
primary_key: :id,
foreign_key: :updated_by
validates_presence_of :code
validates_presence_of :name
validates_presence_of :user
end
user.rb:
class User < ActiveRecord::Base
extend FriendlyId
include Filterable
friendly_id :slug_candidates, use: :history
has_secure_password
acts_as_paranoid
has_many :activities
has_many :pricing_histories
has_many :commodity_groups
end

CommodityGroup is searching for user_id in commodity_groups table.
You need to specify that in the User model.
class User < ActiveRecord::Base
extend FriendlyId
include Filterable
friendly_id :slug_candidates, use: :history
has_secure_password
acts_as_paranoid
has_many :activities
has_many :pricing_histories
has_many :commodity_groups_created, class_name: 'CommodityGroup',
foreign_key: :created_by
has_many :commodity_groups_updated, class_name: 'CommodityGroup',
foreign_key: :updated_by
end

you've got the foreign_key on the CommodityGroup side, you just need to add it to the User side as well and you should be good to go eg:
has_many :commodity_groups, class_name: 'User', foreign_key: :created_by
(you may need to jigger with the details to figure out what else you need to add, but this will basically give you the idea on how to solve it)

Related

How to require users to request group membership?

I have a User model, a group model (Cliq), and a group_membership model (Cliq_Membership). Everything seems to be working fine so far. I currently have it so that when a User creates a group they "own" it and when an "owner" leaves the group (destroys their group membership) the entire group is destroyed. A group has one owner and many members. I want to make it so that a User has to request to be a "member". I want the "owner" to be the only one to see the requests and accept/deny the requests.
For Clarity:
I want users to have to request to be group members
I want Cliqs to be able to request Users to be members
Only the owner should be able to see/accept/deny friend requests
I want the relationship to be "two-way/self-referential"; that is, I want the User/Member to be shown as being included in the group and the group as having another member
Cliqs = Groups
How do you accomplish this?
Here is my code so far:
Models:
class User < ActiveRecord::Base
has_many :uploads
has_one :owned_cliq, foreign_key: 'owner_id', class_name: 'Cliq', dependent: :destroy
has_many :cliq_memberships
has_many :cliqs, through: :cliq_memberships
end
class CliqMembership < ActiveRecord::Base
belongs_to :cliq
belongs_to :user
end
class Cliq < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
has_many :cliq_memberships, dependent: :destroy
has_many :members, through: :cliq_memberships, source: :user
end
Controllers:
class CliqMembershipsController < ApplicationController
def create
#Cliq or Cliq_ID?
#cliq = Cliq.find(params[:cliq])
#cliq_membership = current_user.cliq_memberships.build(cliq: #cliq)
#cliq.members << current_user
if #cliq_membership.save
flash[:notice] = "Joined #{#cliq.name}"
else
flash[:notice] = "Not able to join Cliq."
end
redirect_to cliq_url
end
def destroy
#cliq_membership = current_user.cliq_memberships.find(params[:id])
#cliq = #cliq_membership.cliq
if #cliq.owner == current_user
#cliq.destroy
flash[:notice] = "Cliq has been deleted."
redirect_to current_user
else
#cliq_membership.destroy
flash[:notice] = "You left the Cliq."
redirect_to current_user
end
end
end
class CliqsController < ApplicationController
def show
#cliq = Cliq.find(params[:id])
end
def new
#cliq = Cliq.new(params[:id])
end
def create
#cliq = current_user.build_owned_cliq(cliq_params)
#cliq.members << current_user
if #cliq.save
redirect_to current_user
else
redirect_to new_cliq_path
end
end
def destroy
##cliq = current_user.owned_cliq.find(params[:id])
#lash[:alert] = "Are you sure you want to delete your Cliq? Your Cliq and all of its associations will be permanently deleted."
##cliq.destroy
#if #cliq.destroy
#redirect_to current_user
#flash[:notice] = "You deleted the Cliq."
#else
#redirect_to current_user
#set up error handler
#flash[:notice] = "Failed to delete Cliq."
#end
end
def cliq_params
params.require(:cliq).permit(:name, :cliq_id)
end
end
You can create another model and controller for handling user requests
create request.rb model
class Request < ActiveRecord::Base
belongs_to :user
belongs_to :cliq
end
create requests_controller.rb.
class RequestsController < ApplicationController
before_action :set_group
before_action :auth_group_owner
before_action :find_request, except: [:index, :create]
def index
end
def create
#grp.requests.where(user_id: current_user.id).first_or_create
# redirect the user
end
def approv
# add the user to the group
#request.destroy
# redirect
end
def destroy
#delete the request
end
private
def set_group
#find group #grp
end
def auth_group_owner
if current_user != #grp.owner
redirect
end
end
def find_request
#find request
end
end
your routes.rb
resources :groups do
resources :requests, only: [:index, :destroy] do
member do
get 'approv'
end
end
end
The following solution should work without creating a new model for requests. Adding a new boolean field to CliqMembership model to store whether a particular cliq_memberhip is confirmed or not is sufficient. (Let's call that field 'confirmed', for example)
class User < ActiveRecord::Base
has_many :cliq_memberships
has_many :cliqs, through: :cliq_memberships
has_many :confirmed_memberships, -> { confirmed }, class_name: "CliqMembership"
has_many :confirmed_cliqs, through: :confirmed_memberships, source: :cliq
end
class CliqMembership < ActiveRecord::Base
belongs_to :cliq
belongs_to :user
scope :confirmed, -> { where(confirmed: true) }
end
class Cliq < ActiveRecord::Base
has_many :cliq_memberships, dependent: :destroy
has_many :members, through: :cliq_memberships, source: :user
has_many :confirmed_memberships, { confirmed }, class_name: "CliqMembership"
has_many :confirmed_members, through: :confirmed_memberships, source: :user
end
With this, you can set the value of confirmed field to false by default when a new cliq_membership is created by a user. Until the owner update's that particular cliq_membership to change the value of confirmed to true.
Assuming user & cliq are instances of User model & Cliq model respectively, you can now use user.confirmed_cliqs and cliq.confirmed_members.
Edit:
In order to restrict the edit & update actions on cliq_membership to only the cliq owner, you can use a before filter.
class CliqMembershipsController < ApplicationController
before_action :cliq_owner, only: [:edit, :update]
def edit
#cliq_membership = CliqMembership.find(params[:id])
end
def update
#cliq_membership = CliqMembership.find(params[:id])
#cliq_membership.update_attributes(cliq_membership_params)
end
private
def cliq_membership_params
params.require(:cliq_membership).permit(:cliq_id, :user_id, :confirmed)
end
def cliq_owner
#cliq = CliqMembership.find(params[:id]).cliq
redirect_to root_url unless #cliq.owner == current_user
end
end
Hope it works for you.

has_many :through 4r post contributions

I'm having a bit of trouble understanding how to setup the contributions controller and the form in the view. I've set some forms in the view so i know the join tables work.
As of right now a post belongs_to user && a user has_many posts
Objective:
1. user1 creates post - which belongs to user1
2. user2 requesting to join the user1_post as a contributor
3. user1 accepts or declines request
4. user2 is now a contributor to user1_post
5. user1 can remove user2 as a contributor
Got the has_many :through setup properly and have tested it in the console
contribution.rb
class Contribution < ActiveRecord::Base
belongs_to :post
belongs_to :user
def accept
self.accepted = true
end
end
post.rb
class Post < ActiveRecord::Base
belongs_to :author, class_name: 'User'
has_many :contribution_requests, -> { where(accepted: false) }, class_name: 'Contribution'
has_many :contributions, -> { where(accepted: true) }
has_many :contributors, through: :contributions, source: :user
end
user.rb
class User < ActiveRecord::Base
has_many :posts, foreign_key: 'author_id'
has_many :contribution_requests, -> { where(accepted: false) }, class_name: 'Contribution'
has_many :contributions, -> { where(accepted: true) }
has_many :contributed_posts, through: :contributions, source: :post
end
contributions_controller.rb
class ContributionsController < ApplicationController
def create
#contribution = current_user.contributions.build(:user_id => params[:id])
if #contribution.save
flash[:notice] = "Added contributor."
redirect_to posts_path(#post)
else
flash[:error] = "Unable to add contributor."
redirect_to posts_path(#post)
end
end
def destroy
#contribution = current_user.contributions.find(params[:id])
#contribution.destroy
flash[:notice] = "Removed contributor."
redirect_to root_url
end
end
Without much context, this is what I'd do:
#config/routes.rb
resources :posts do
resources :contributions, only: [:create, :destroy] #-> can use posts#edit to add extra contributions
end
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def edit
#post = Post.find params[:id]
end
end
#app/views/contributions/edit.html.erb
<%= form_for #post do |f| %>
# #post form
<% end %>
## contributor add / remove form (select boxes)
#app/controllers/contributions_controller.rb
class ContributionsController < ApplicationController
def create
#post = Post.find params[:post_id]
#contribution = current_user.contributions.new contribution_params
#contribution.post = #post
notice = #contribution.save ? "Added Contributor" : "Unable to add contributor"
redirect_to #post, notice: notice
end
def destroy
#contribution = current_user.contributions.find params[:id]
#contribution.destroy
redirect_to root_url, notice: "Removed Contributor"
end
private
def contribution_params
params.require(:contribution).permit(:user, :post, :accepted)
end
end
As an aside, you should look at an ActiveRecordExtension to give you some methods for your conbtributions association (instead of having multiple associations):
#app/models/post.rb
class Post < ActiveRecord::Base
has_many :contributions, -> { extending ContributionExtension }
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :contributions, -> { extending ContributionExtension }
end
#app/models/concerns/contribution_extension.rb
class ContributionExtension
def requests(status=false)
where accepted: status
end
def accepted(status=true)
where accepted: status
end
end
#post.contirbutions.requets
#post.contributions.accepted
#user.contributions.requests
#user.contributions.accepted
--
And also, you should look at implementing a state_machine for your Contribution model:
#app/models/contribution.rb
class Contribution < ActiveRecord::Base
state_machine :accepted, initial: :pending do
event :accept do
transition [:pending, :denied] => :accepted
end
event :deny do
transition [:pending, :accepted] => :denied
end
end
end
Great article about it here.
This will allow you to call:
#contribution = current_user.contributions.find params[:id]
#contribution.accept
It will also give you several other cool methods:
#contribution.accepted?
#contribution.state

Match multiple models in single Rails route with friendly_id

I have a Company and a User model, both with a slug via friendly_id. Slugs are ensured to be unique across both models.
I'd like to have URLs:
http://www.example.com/any_company_name
http://www.example.com/any_user_name
e.g. both /apple and /tim
I'm not sure how to achieve this in Rails.
I have tried various permutations of:
routes.rb:
resources :users, path: ''
resources :companies, path: ''
get '*search', to: 'my_controller#redirect'
and
my_controller#redirect:
#company = Company.friendly.find(params[:search])
redirect_to #company if #company
#user = User.friendly.find(params[:search])
redirect_to #user if #user
However I can't get it to work. I can get /apple to redirect to /companies/apple and /tim to redirect to /users/tim (by removing the path: '' option) but this is not what I want to achieve.
Instead redirect with urls, you can redirect with controller#action by using url_for.
For example:
my_controller#redirect:
#company = Company.friendly.find(params[:search])
redirect_to url_for(action: 'show', controller: :companies , status: :success, company_id:#company.id)
#user = User.friendly.find(params[:search])
redirect_to url_for(action: 'show', controller: :users, status: :success,user_id:#user.id)
I had a similar problem and was able to solve it by creating a PublicSlug model with a slug attribute and a polymorphic association to a "Sluggable" class. I also used a Sluggable concern that I included in models I would like to query.
The PublicSlug model
class PublicSlug < ActiveRecord::Base
extend FriendlyId
friendly_id :sluggable_name, use: :slugged
belongs_to :sluggable, polymorphic: true
private
# Generate the slug based on the title of the Sluggable class
def sluggable_name
sluggable.name
end
end
The Sluggable concern
module Sluggable
extend ActiveSupport::Concern
included do
has_one :public_slug, dependent: :destroy, as: :sluggable
delegate :slug, to: :public_slug
end
end
Company and User models.
class User < ActiveRecord::Base
include Sluggable
end
class Company < ActiveRecord::Base
include Sluggable
end
I can now query both models using
Sluggable.friendly.find(slug).sluggable
The redirect could be handled in your controller as follows:
def redirect
#sluggable = Sluggable.friendly.find(params[:search]).sluggable
redirect_to #sluggable
end
Building off of #Essn's answer...
Still use a PublicSlug model:
# app/models/public_slug.rb
class PublicSlug < ActiveRecord::Base
extend FriendlyId
friendly_id :sluggable_name, use: :slugged
belongs_to :sluggable, polymorphic: true
validates :slug, presence: true, uniqueness: { case_sensitive: false }
private
# Generate the slug based on the title of the Sluggable class
def sluggable_name
sluggable.name
end
end
And a Sluggable concern:
# app/models/concerns/sluggable.rb
module Sluggable
extend ActiveSupport::Concern
included do
before_validation :create_public_slug
has_one :public_slug, dependent: :destroy, as: :sluggable
delegate :slug, to: :public_slug
private
def create_public_slug
self.public_slug = PublicSlug.new unless public_slug.present?
end
end
end
Include that concern in all models you want to lookup:
# app/models/user.rb
class User < ActiveRecord::Base
include Sluggable
...
end
Create a migration:
# db/migrate/...create_public_slugs.rb
class CreatePublicSlugs < ActiveRecord::Migration[5.0]
def change
create_table :public_slugs do |t|
t.references :sluggable, polymorphic: true
t.string :slug
end
end
end
Then you can lookup the model via:
# app/controllers/home_controller.rb
class HomeController < ApplicationController
# /:slug
def show
#sluggable = PublicSlug.friendly.find(params[:id]).sluggable
render #sluggable
end
end

How to create another object when creating a Devise User from their registration form in Rails?

There are different kinds of users in my system. One kind is, let's say, a designer:
class Designer < ActiveRecord::Base
attr_accessible :user_id, :portfolio_id, :some_designer_specific_field
belongs_to :user
belongs_to :portfolio
end
That is created immediately when the user signs up. So when a user fills out the sign_up form, a Devise User is created along with this Designer object with its user_id set to the new User that was created. It's easy enough if I have access to the code of the controller. But with Devise, I don't have access to this registration controller.
What's the proper way to create a User and Designer upon registration?
In a recent project I've used the form object pattern to create both a Devise user and a company in one step. This involves bypassing Devise's RegistrationsController and creating your own SignupsController.
# config/routes.rb
# Signups
get 'signup' => 'signups#new', as: :new_signup
post 'signup' => 'signups#create', as: :signups
# app/controllers/signups_controller.rb
class SignupsController < ApplicationController
def new
#signup = Signup.new
end
def create
#signup = Signup.new(params[:signup])
if #signup.save
sign_in #signup.user
redirect_to projects_path, notice: 'You signed up successfully.'
else
render action: :new
end
end
end
The referenced signup model is defined as a form object.
# app/models/signup.rb
# The signup class is a form object class that helps with
# creating a user, account and project all in one step and form
class Signup
# Available in Rails 4
include ActiveModel::Model
attr_reader :user
attr_reader :account
attr_reader :membership
attr_accessor :name
attr_accessor :company_name
attr_accessor :email
attr_accessor :password
validates :name, :company_name, :email, :password, presence: true
def save
# Validate signup object
return false unless valid?
delegate_attributes_for_user
delegate_attributes_for_account
delegate_errors_for_user unless #user.valid?
delegate_errors_for_account unless #account.valid?
# Have any errors been added by validating user and account?
if !errors.any?
persist!
true
else
false
end
end
private
def delegate_attributes_for_user
#user = User.new do |user|
user.name = name
user.email = email
user.password = password
user.password_confirmation = password
end
end
def delegate_attributes_for_account
#account = Account.new do |account|
account.name = company_name
end
end
def delegate_errors_for_user
errors.add(:name, #user.errors[:name].first) if #user.errors[:name].present?
errors.add(:email, #user.errors[:email].first) if #user.errors[:email].present?
errors.add(:password, #user.errors[:password].first) if #user.errors[:password].present?
end
def delegate_errors_for_account
errors.add(:company_name, #account.errors[:name].first) if #account.errors[:name].present?
end
def persist!
#user.save!
#account.save!
create_admin_membership
end
def create_admin_membership
#membership = Membership.create! do |membership|
membership.user = #user
membership.account = #account
membership.admin = true
end
end
end
An excellent read on form objects (and source for my work) is this CodeClimate blog post on Refactoring.
In all, I prefer this approach vastly over using accepts_nested_attributes_for, though there might be even greater ways out there. Let me know if you find one!
===
Edit: Added the referenced models and their associations for better understanding.
class User < ActiveRecord::Base
# Memberships and accounts
has_many :memberships
has_many :accounts, through: :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :account
end
class Account < ActiveRecord::Base
# Memberships and members
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships
has_many :admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => true }
has_many :non_admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => false }
end
This structure in the model is modeled alongside saucy, a gem by thoughtbot. The source is not on Github AFAIK, but can extract it from the gem. I've been learning a lot by remodeling it.
If you don't want to change the registration controller, one way is to use the ActiveRecord callbacks
class User < ActiveRecord::Base
after_create :create_designer
private
def create_designer
Designer.create(user_id: self.id)
end
end

Has_many through - One Sided Uniqueness in the database

I have a three models:
class Feed < ActiveRecord::Base
has_many :filters, :dependent => :destroy
has_many :keywords, :through => :filters, :uniq => true
end
class Filter < ActiveRecord::Base
belongs_to :feed
belongs_to :keyword
validates_uniqueness_of :keyword_id, :scope => :feed_id
end
class Keyword < ActiveRecord::Base
has_many :filters, :dependent => :destroy
has_many :feeds, :through => :filters
end
What I want is to have only unique entries in the database for keywords. For example, if two feeds both have a keyword 'hello', there should be two filters (one for each feed) both pointing to the same keyword.
What I am having trouble with is the controller code. Perhaps I am looking for too simple a solution, but I figure there must be an easy way to do this. This is what I have in my create action so far:
def create
#feed = Feed.find(params[:feed_id])
#keyword = #feed.keywords.create(params[:keyword])
redirect_to feed_keywords_path(#feed), notice: 'Keyword added successfully.'
end
With this controller code, the previous example would result in a duplicate keyword in the database, one for each feed/filter. Is there a straight-forward solution to this or do I need to do a check beforehand to see if there is already a keyword and in that case just create the filter?
Use a dynamic finder find_or_create_by :
def create
#feed = Feed.find(params[:feed_id])
#keyword = Keyword.find_or_create_by_keyword(params[:keyword]) # I assume here that you have a column 'keyword' in your 'keywords' table
#feed.keywords << #keyword unless #feed.keywords.all.include?(#keyword)
redirect_to feed_keywords_path(#feed), notice: 'Keyword added successfully.'
end

Resources