I am not very good at learning how to code. I've been trying for 4 years and still struggling to figure out basic concepts.
I haven't found the right starting point so, I'm constantly gap filling for things that might be foundations and I don't know them yet.
I have a Rails 5 app. It has models for user, roles, app_roles and assign_roles.
The associations are:
User
rolify strict: true # strict means you get true only on a role that you manually add
attr_accessor :current_role
belongs_to :organisation
Role
has_and_belongs_to_many :users, :join_table => :users_roles
belongs_to :resource,
:polymorphic => true,
:optional => true
AppRole
[no associations]
Note: this is the resource I'm using to have a CRUD to define roles that the app can use to assign roles to users.
Assign Role
[no associations]
Note: this is the resource I'm using to allow some users to assign roles to some other users.
Organisation
has_many :users
I'm using rolify gem.
My user model has:
class User < ApplicationRecord
rolify strict: true # strict means you get true only on a role that you manually add
attr_accessor :current_role
My Roles table has:
class Role < ApplicationRecord
has_and_belongs_to_many :users, :join_table => :users_roles
belongs_to :resource,
:polymorphic => true,
:optional => true
validates :resource_type,
:inclusion => { :in => Rolify.resource_types },
:allow_nil => true
scopify
end
In my assign_roles controller, I have:
class Users::AssignRolesController < ApplicationController
before_action :authenticate_user!
def index
# if #current_user.is_admin?
#app_roles = AppRole.all
# else
# #app_roles = AppRole.where(category: relevant_category)
# end
# if #current_user.is_admin?
#users = User.all
# else
# #users = current_user.organisation.users.select { |u| u.id != current_user.organisation.owner_id }
# end
end
def create
user = User.find(params[:users])
role = AppRole.find(params[:roles])
# organisation = Organisation.first
# #organisation = Organisation.find(#current_user.organisation)
# byebug
user.add_role role.display_name, #current_user.organisation
flash[:notice] = "Successfully created"
redirect_to action: :index
end
def show
# #users = User.joins(:profiles).where('profiles.organisation_id = ?' #current_user.organisation.id)
# #users = User.all
#current_user.organisation.users
end
def update
end
def destroy
user = User.find(params[:users])
# role = AppRole.find(params[:roles])
assigned_role = user.roles
# user_roles = user.roles
# organisation = Organisation.first
# byebug
user.remove_role assigned_role.name, #current_user.organisation
flash[:notice] = "Successfully created"
redirect_to action: :index
end
end
In my routes file, i have:
resources :users, shallow: true do
scope module: :users do
resources :assign_roles
resources :identities
end
end
In my views/users/assign_roles/index file, I have:
<%= form_tag(url: '/assign_roles', method: :post ) do |f| %>
<div class="row" style="margin-top:50px; margin-bottom: 150px">
<div class="col-md-3 col-md-offset-1">
<%= select_tag "users", options_from_collection_for_select(#users, "id", "full_name"), { class: "chosen-select form-control" } %>
</div>
<!-- # roles -->
<div class="col-md-3 col-md-offset-1">
<%= select_tag "roles", options_from_collection_for_select(#app_roles, "id", "display_name"), :class => 'chosen-select form-control' %>
</div>
<div class="col-md-3 col-md-offset-1">
<div class="form-actions">
<%= submit_tag(value = "Submit") %>
</div>
</div>
</div>
<% end %>
So far, that all looks OK. Im' getting stuck at the next part.
In my views/users/assign_roles/show.html.er
I'm trying to show each user's roles on a view. I want the user that assigned any existing roles to be able to delete them.
I have:
<% #users.each do |user| %>
<td><%= user.roles.count %></td>
<% user.roles.each do |role| %>
<td><%= role.name.titleize %></td>
<td><%= link_to 'Destroy', role, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% end %>
<% end %>
My controller for assign_roles is saved in app/controllers/users folder. My views folder for assign roles is saved inside app/views/users/assign_roles
When I try to use the app/users/4/assign_roles form, I get the form to render with the list of AppRoles that are available to be assigned. I don't get any error message. Instead, I get the notice on successful creation. However, when I try to check if a user has a role, I get false.
I can see the server log reads as follows:
Started POST "/users/4/assign_roles?method=post&url=%2Fassign_roles" for ::1 at 2016-10-23 11:50:11 +1100
Processing by Users::AssignRolesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"U80gMQd01SaTcHHnnSFxvRc2u9JvJMFB+5smS9SaN8ZRixQvJRTMbutG0KkoqXL+oMU1aOxX8AURBtuy2Rm5yA==", "users"=>"4", "roles"=>"3", "commit"=>"Submit", "method"=>"post", "url"=>"/assign_roles", "user_id"=>"4"}
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 4], ["LIMIT", 1]]
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 4], ["LIMIT", 1]]
AppRole Load (0.2ms) SELECT "app_roles".* FROM "app_roles" WHERE "app_roles"."id" = $1 LIMIT $2 [["id", 3], ["LIMIT", 1]]
Organisation Load (0.4ms) SELECT "organisations".* FROM "organisations" ORDER BY "organisations"."id" ASC LIMIT $1 [["LIMIT", 1]]
Role Load (1.1ms) SELECT "roles".* FROM "roles" WHERE "roles"."name" = $1 AND "roles"."resource_type" = $2 AND "roles"."resource_id" = $3 ORDER BY "roles"."id" ASC LIMIT $4 [["name", "sdfddd"], ["resource_type", "Organisation"], ["resource_id", 1], ["LIMIT", 1]]
(0.2ms) BEGIN
(0.1ms) ROLLBACK
HABTM_Roles Load (0.4ms) SELECT "users_roles".* FROM "users_roles" WHERE "users_roles"."user_id" = $1 [["user_id", 4]]
Role Load (0.3ms) SELECT "roles".* FROM "roles" WHERE "roles"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.5ms) SELECT "roles".id FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 [["user_id", 4]]
Role Load (0.3ms) SELECT "roles".* FROM "roles" WHERE "roles"."id" = 1
Role Load (0.4ms) SELECT "roles".* FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 [["user_id", 4]]
Redirected to http://localhost:3000/users/4/assign_roles
Completed 302 Found in 33ms (ActiveRecord: 6.5ms)
I don't know what the ROLLBACK line means but I don't think that usually appears in these log messages when things are working correctly.
Can anyone see what I need to do to get the role assignment working correctly?
organisation model
class Organisation < ApplicationRecord
include LogoUploader[:logo]
include BannerUploader[:banner]
# --------------- associations
has_many :users
# --------------- scopes
# --------------- validations
# --------------- class methods
enum org_type: {
University: 1,
Publicly_Funded_Research_Organisation: 2,
Industry: 3,
Grantor: 4,
Investor: 5,
Policy: 6,
}
# --------------- callbacks
# --------------- instance methods
# --------------- private methods
end
add resourcify to your organisation model and then try.
Instead of developing our own RBAC (Role Based Access Control), there are already some open source gems available in rails community. we can make use of that.
Its a nice gem
https://github.com/nathanl/authority
Also there are many other gems available
https://www.ruby-toolbox.com/categories/rails_authorization
If none of the above gems fulfil your need, then atleast you can use that as an sample model for your development.
Related
I'm trying to add existing Tags to Articles.
My models:
# app/models/user.rb
class User < ApplicationRecord
has_many :articles, dependent: :destroy
has_many :tags, dependent: :destroy
end
# app/models/article.rb
class Article < ApplicationRecord
belongs_to :user
has_many :tags
end
# app/models/tag.rb
class Tag < ApplicationRecord
belongs_to :user
belongs_to :article
has_many :articles
validates :name, presence: true, uniqueness: { scope: :user_id }
end
By using the following in the console, I'm able to add a tag to an article.
> a = Article.last
> a.tags.create(name: "unread", user_id: "1")
> a.tags
Tag Load (0.3ms) SELECT "tags".* FROM "tags" WHERE "tags"."article_id" = ? [["article_id", 29]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Tag id: 6, name: "unread", user_id: 1, article_id: 29, created_at: "2020-12-28 16:05:36", updated_at: "2020-12-28 16:05:36", permalink: "unread">]>
If I try to add this same tag to different article using the same .create, I get a rollback error.
> a = Article.first
> a.tags.create(name: "unread", user_id: "1")
(0.0ms) begin transaction
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Tag Exists? (0.1ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = ? AND "tags"."user_id" = ? LIMIT ? [["name", "unread"], ["user_id", 1], ["LIMIT", 1]]
(0.0ms) rollback transaction
Thinking the problem is the .create since that tag already exists, I tried this first_or_create, but that also errored.
> a.tags.first_or_create(name: "unread", user_id: "1")
Tag Load (0.1ms) SELECT "tags".* FROM "tags" WHERE "tags"."article_id" = ? ORDER BY "tags"."id" ASC LIMIT ? [["article_id", 52], ["LIMIT", 1]]
(0.0ms) begin transaction
Tag Exists? (0.1ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = ? AND "tags"."user_id" = ? LIMIT ? [["name", "unread"], ["user_id", 1], ["LIMIT", 1]]
(0.3ms) rollback transaction
How do I add an existing tag to an article?
Edit:
I've added a join table as prescribed by Max. This has allowed me to save tags to articles via the console.
> t = Tag.where(name: "rails", user_id: "1").first_or_create
Tag Load (0.6ms) SELECT "tags".* FROM "tags" WHERE "tags"."name" = ? AND "tags"."user_id" = ? ORDER BY "tags"."id" ASC LIMIT ? [["name", "rails"], ["user_id", 1], ["LIMIT", 1]]
=> #<Tag id: 8, name: "rails", user_id: 1, created_at: "2020-12-28 21:21:25", updated_at: "2020-12-28 21:21:25", permalink: "rails">
> a.tags << t
(0.0ms) begin transaction
Article Tag Exists? (0.1ms) SELECT 1 AS one FROM "article_tags" WHERE "article_tags"."tag_id" = ? AND "article_tags"."article_id" = ? LIMIT ? [["tag_id", 8], ["article_id", 29], ["LIMIT", 1]]
Article Tag Create (0.2ms) INSERT INTO "article_tags" ("article_id", "tag_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["article_id", 29], ["tag_id", 8], ["created_at", "2020-12-28 21:22:13.567187"], ["updated_at", "2020-12-28 21:22:13.567187"]]
(17.9ms) commit transaction
Tag Load (0.1ms) SELECT "tags".* FROM "tags" INNER JOIN "article_tags" ON "tags"."id" = "article_tags"."tag_id" WHERE "article_tags"."article_id" = ? LIMIT ? [["article_id", 29], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Tag id: 6, name: "unread", user_id: 1, created_at: "2020-12-28 16:05:36", updated_at: "2020-12-28 16:05:36", permalink: "unread">, #<Tag id: 8, name: "rails", user_id: 1, created_at: "2020-12-28 21:21:25", updated_at: "2020-12-28 21:21:25", permalink: "rails">]>
I'm still unclear how to save this from the view.
I have a form in app/views/articles/edit.html.erb:
<%= form_with(model: #article, local: true) do |form| %>
<%= form.label :tag_list, "Tags, separated by comma" %>
<%= form.text_field :tag_list %>
<%= form.submit "Update Tags" %>
<% end %>
In the Articles Controller I assume I'd want the following.
def edit
#article.tags << Tag.where(name: "unread", user_id: "1").first_or_create
#article.save!
end
...or perhaps I need to loop those tags in the model? Something like this:
# app/models/article.rb
def tag_list
tags.map(&:name).join(", ")
end
def tag_list=(names)
self.tags = names.split(",").map do |name|
Tag.where(name: name.strip, user_id: current_user.id).first_or_create!
end
end
# app/controllers/articles_controller.rb
def edit
#article.tags << tag_list
#article.save!
end
But this isn't working. How do I update the tags from a view?
Edit:
I've decided not to update tags by form, so I've marked Max's answer as the solution since he pointed me to the Join Tables which got me on the right track.
If you wish to update this for posterity, I'm sure they will appreciate it.
If you want a tag to ever belong to more then a single article you need a join table:
class Article < ApplicationRecord
has_many :article_tags
has_many :tags, through: :article_tags
end
# rails g model article_tag article:references tag:references
# make sure to add a unique index on article_id and tag_id
class ArticleTag < ApplicationRecord
belongs_to :article
belongs_to :tag
validates_uniqueness_of :tag_id, scope: :article_id
end
class Tag < ApplicationRecord
has_many :article_tags
has_many :articles, through: :article_tags
end
This creates a many to many association between the two tables (an article can have multiple tags, and a tag can belong to multiple articles). This can also be done through has_and_belongs_to_many which does not have a join model but has limited flexibility.
If you want to add an existing tag(s) to an article you can use the shovel operator:
a = Article.last
a.tags << Tag.first
a.tags << Tag.second
But usually you use the tag_ids= setter to set the association from an array of ids:
class ArticlesController
def create
#article = Article.new(article_params)
# ...
end
private
def article_params
require(:article)
.permit(:title, :body, tag_ids: [])
end
end
<%= form_with(model: #article) do |f| %>
# ...
<%= f.collection_checkboxes :tag_ids, Tag.all, :id, :name %>
<% end %>
tag_ids: [] permits an array of ids.
This will automatically add/remove rows in the join table depending on what the user checks.
I have these models:
class KlassOccurrence < ApplicationRecord
belongs_to :klass
end
class Klass < ApplicationRecord
has_many :klass_occurrences
belongs_to :user
def next_occurrence_date
self.klass_occurrences.order(scheduled_at: :desc).first
end
end
And I have this in a controller:
#klasses = Klass.includes(:klass_occurrences).where(user_id: current_user.id)
And in a view I have this:
<% #klasses.each do |klass| %>
<tr>
<td><%= klass.next_occurrence_date %></td>
</tr>
<% end %>
I would expect that because of the includes(:klass_occurrences) I wouldn't be seeing an N+1, but this is what I see in the logs:
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 3], ["LIMIT", 1]]
↳ app/controllers/klasses_controller.rb:4:in `index'
Rendering klasses/index.html.erb within layouts/dashboard_layout
Klass Load (0.2ms) SELECT "klasses".* FROM "klasses" WHERE "klasses"."user_id" = $1 [["user_id", 3]]
↳ app/views/klasses/index.html.erb:13
KlassOccurrence Load (4.3ms) SELECT "klass_occurrences".* FROM "klass_occurrences" WHERE "klass_occurrences"."klass_id" IN ($1, $2) [["klass_id", 1], ["klass_id", 5]]
↳ app/views/klasses/index.html.erb:13
KlassOccurrence Load (1.8ms) SELECT "klass_occurrences".* FROM "klass_occurrences" WHERE "klass_occurrences"."klass_id" = $1 ORDER BY "klass_occurrences"."scheduled_at" DESC LIMIT $2 [["klass_id", 1], ["LIMIT", 1]]
↳ app/models/klass.rb:6:in `next_occurrence_date'
KlassOccurrence Load (0.3ms) SELECT "klass_occurrences".* FROM "klass_occurrences" WHERE "klass_occurrences"."klass_id" = $1 ORDER BY "klass_occurrences"."scheduled_at" DESC LIMIT $2 [["klass_id", 5], ["LIMIT", 1]]
↳ app/models/klass.rb:6:in `next_occurrence_date'
Why is it doing three queries for klass occurrences? I would expect just one query to load all the occurrences for all the klasses, but then I see two extra queries.
If you just need the highest value of that column you can also just select an aggregate off the join table:
class Klass < ApplicationRecord
has_many :klass_occurrences
belongs_to :user
def self.with_next_occurrence_date
self.select(
'klass.*',
'MAX(klass_occurrences.scheduled_at) AS next_occurrence_date'
)
.left_joins(:klass_occurrences)
.group(:id)
end
end
#klasses = Klass.with_next_occurrence_date.where(user_id: current_user.id)
<% #klasses.each do |klass| %>
<tr>
<td><%= klass.next_occurrence_date %></td>
</tr>
<% end %>
If you really need a model object you can do it by sorting the records in Ruby:
class Klass < ApplicationRecord
has_many :klass_occurrences
belongs_to :user
def next_occurrence_date
self.klass_occurrences.sort(&:scheduled_at).last
end
end
And there are also a bunch of novel tricks to get ActiveRecord to load just one record off the association that are somewhat out of scope for this question.
i am building an eCommerce app in rails which has two products Chairs and Bookcabinets, i have created two different controllers and models for chairs and bookcabinets, it all worked now i wanted the user to be able to add these products to a cart so i created a ordering system from scratch by watching a tutorial, it worked for chairs but when i add bookcabinets to shopping_cart model it gives me an error: Couldn't find Chair with 'id'=
Server Log
Started POST "/cart/items" for 127.0.0.1 at 2018-09-26 14:16:43 +0530
(0.7ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
Processing by OrderItemsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"wGAl7gZGXipKt7EzzZT1LuBgn2k8KnlaPagp0cQ3l6pIkr6mx8MdJAuhgkY7EEttrHjTSSpcRjqe0qZ0a2hrAA==", "bookcabinet_id"=>"1", "quantity"=>"40"}
Chair Load (0.8ms) SELECT "chairs".* FROM "chairs" WHERE "chairs"."id" = ? LIMIT ? [["id", nil], ["LIMIT", 1]]
Completed 404 Not Found in 106ms (ActiveRecord: 3.8ms)
ActiveRecord::RecordNotFound (Couldn't find Chair with 'id'=):
app/models/shopping_cart.rb:20:in `add_item'
app/controllers/order_items_controller.rb:8:in `create'
My Order_items_controller
class OrderItemsController < ApplicationController
def index
#items = current_cart.order.items
end
def create
current_cart.add_item(
chair_id: params[:chair_id],
bookcabinet_id: params[:bookcabinet_id],
quantity: params[:quantity]
)
redirect_to cart_path
end
def destroy
current_cart.remove_item(id: params[:id])
redirect_to cart_path
end
end
My Order_controller
class OrdersController < ApplicationController
def new
#order = current_cart.order
end
end
** My Order model**
class Order < ApplicationRecord
has_many :items, class_name: 'OrderItem'
end
My Order_item Model
class OrderItem < ApplicationRecord
belongs_to :order
belongs_to :chair
belongs_to :bookcabinet
end
My Shopping_Cart Model
class ShoppingCart
delegate :sub_total, to: :order
def initialize(token:)
#token = token
end
def order
#order ||= Order.find_or_create_by(token: #token) do |order|
order.sub_total = 0
end
end
def items_count
order.items.sum(:quantity)
end
def add_item(chair_id:, bookcabinet_id:, quantity: 1)
chair = Chair.find(chair_id),
bookcabinet = Bookcabinet.find(bookcabinet_id)
order_item = order.items.find_or_initialize_by(
chair_id: chair_id,
bookcabinet_id: bookcabinet_id
)
order_item.price = chair.price
order_item.price = bookcabinet.price
order_item.quantity = quantity
ActiveRecord::Base.transaction do
order_item.save
update_sub_total!
end
end
def remove_item(id:)
ActiveRecord::Base.transaction do
order.items.destroy(id)
update_sub_total!
end
end
private
def update_sub_total!
order.sub_total = order.items.sum('quantity*price')
order.save
end
end
My Create_order migration
class CreateOrders < ActiveRecord::Migration[5.1]
def change
create_table :orders do |t|
t.string :first_name
t.string :last_name, null: false
t.decimal :sub_total, precision: 15, scale: 2, null: false
t.timestamps
end
end
end
my CreateOrderItems migration
class CreateOrderItems < ActiveRecord::Migration[5.1]
def change
create_table :order_items do |t|
t.belongs_to :order, null: false
t.belongs_to :chair, null: false
t.belongs_to :bookcabinet
t.integer :quantity, null: false
t.decimal :price, precision: 15, scale: 2, null: false
t.timestamps
end
end
end
Routes
get '/cart', to: 'order_items#index'
resources :order_items, path: '/cart/items'
get '/cart/checkout', to: 'orders#new', as: :checkout
patch '/cart/checkout', to: 'orders#create'
Add To Cart Form
<div class="button my-2">
<%= form_tag order_items_path do %>
<%= hidden_field_tag :bookcabinet_id, #bookcabinet.id %>
<%= number_field_tag :quantity, 1 %>
<button type="submit" class="btn cart"><i class="d-inline fa fa-cart-arrow-down" aria-hidden="true"></i><p class="d-inline">Add To Cart</p></button>
<% end %>
Updated Server Log
Started POST "/cart/items" for 127.0.0.1 at 2018-09-27 00:24:13 +0530
(0.6ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
Processing by OrderItemsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"gqwsV8Z/0p74Y8WPLMmfk9vmoSPCFpP+lAtVxxH3KSvWUzYYIFtbQRkAzM5yh5HS/wAzelr90LJW64joFUpGwg==", "bookcabinet_id"=>"1", "quantity"=>"40"}
Bookcabinet Load (0.7ms) SELECT "bookcabinets".* FROM "bookcabinets" WHERE "bookcabinets"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Order Load (0.9ms) SELECT "orders".* FROM "orders" WHERE "orders"."token" = ? LIMIT ? [["token", "20897ec5db2636f5"], ["LIMIT", 1]]
OrderItem Load (0.5ms) SELECT "order_items".* FROM "order_items" WHERE "order_items"."order_id" = ? AND "order_items"."chair_id" IS NULL AND "order_items"."bookcabinet_id" = ? LIMIT ? [["order_id", 4], ["bookcabinet_id", 1], ["LIMIT", 1]]
(0.2ms) begin transaction
Bookcabinet Load (0.3ms) SELECT "bookcabinets".* FROM "bookcabinets" WHERE "bookcabinets"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
(0.4ms) SELECT SUM(quantity*price) FROM "order_items" WHERE "order_items"."order_id" = ? [["order_id", 4]]
(0.2ms) commit transaction
Redirected to http://localhost:3000/cart
Completed 302 Found in 557ms (ActiveRecord: 10.4ms)
Started GET "/cart" for 127.0.0.1 at 2018-09-27 00:24:14 +0530
Processing by OrderItemsController#index as HTML
Order Load (0.5ms) SELECT "orders".* FROM "orders" WHERE "orders"."token" = ? LIMIT ? [["token", "20897ec5db2636f5"], ["LIMIT", 1]]
Rendering order_items/index.html.erb within layouts/application
(0.4ms) SELECT SUM("order_items"."quantity") FROM "order_items" WHERE "order_items"."order_id" = ? [["order_id", 4]]
OrderItem Load (1.1ms) SELECT "order_items".* FROM "order_items" WHERE "order_items"."order_id" = ? [["order_id", 4]]
Rendered order_items/index.html.erb within layouts/application (15.0ms)
CACHE (0.1ms) SELECT SUM("order_items"."quantity") FROM "order_items" WHERE "order_items"."order_id" = ? [["order_id", 4]]
Rendered layouts/_header.html.erb (22.0ms)
Rendered layouts/_footer.html.erb (1.9ms)
Completed 200 OK in 1313ms (Views: 1304.9ms | ActiveRecord: 2.1ms)
You are searching for chair item every time you call add_item method so its throwing an error as you are not passing chair id so you need to modify the method as below
def add_item(chair_id:, bookcabinet_id:, quantity: 1)
chair = Chair.find(chair_id) if chair_id
bookcabinet = Bookcabinet.find(bookcabinet_id) if bookcabinet_id
order_item = order.items.find_or_initialize_by(
chair_id: chair_id,
bookcabinet_id: bookcabinet_id
)
order_item.price = chair.price if chair
order_item.price = bookcabinet.price
order_item.quantity = quantity
ActiveRecord::Base.transaction do
order_item.save
update_sub_total!
end
end
Please feel free to ask if there are any questions regarding the same
In my controllers/common/roles_controller.rb I would like to check if particular role (ID) belongs to current_user company users and if not, redirect to errors_path:
def correct_role
role_user = Role.where(:id => params[:id]).select('user_id').first
company_user = current_user.companies.includes(:users)
redirect_to(errors_path) unless company_user.include? role_user.id
end
Definitions:
role_user - finds user ID for particular role ID ("user_id" is column of roles table)
company_user - finds all user ID who belong to companies, which belong to current_user
models/role.rb
belongs_to :user, optional: true, inverse_of: :roles
accepts_nested_attributes_for :user
enum general: { seller: 1, buyer: 2, seller_buyer: 3}, _suffix: true
enum dashboard: { denied: 0, viewer: 1, editer: 2, creater: 3, deleter: 4}, _suffix: true
models/user.rb
#User has roles
has_many :roles
accepts_nested_attributes_for :roles, reject_if: proc { |attributes| attributes[:name].blank? }
# User has many companies
has_many :accounts, dependent: :destroy
has_many :companies, through: :accounts
models/account.rb
class Account < ApplicationRecord
belongs_to :company
belongs_to :user
accepts_nested_attributes_for :company, :user
end
models/company.rb
has_many :accounts, dependent: :destroy
has_many :users, through: :accounts
At the moment with role_user and company_user I can find both ID, however I cannot do the checking part. How do I do that correctly, please? Thank you for any help!
Update
#sajan code give this in console when I open /common/roles/1/edit (current_user ID=1 and should be allowed to edit):
Parameters: {"id"=>"1"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
(0.6ms) SELECT COUNT(*) FROM "companies" INNER JOIN "accounts" ON "companies"."id" = "accounts"."company_id" WHERE "accounts"."user_id" = ? [["user_id", 1]]
(0.3ms) SELECT "roles".id FROM "roles" WHERE "roles"."user_id" = ? [["user_id", 1]]
#Abhishek Kumar code in console gives:
Parameters: {"id"=>"1"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Company Load (0.5ms) SELECT "companies".* FROM "companies" INNER JOIN "accounts" ON "companies"."id" = "accounts"."company_id" WHERE "accounts"."user_id" = ? [["user_id", 1]]
(0.4ms) SELECT "users".id FROM "users" INNER JOIN "accounts" ON "users"."id" = "accounts"."user_id" WHERE "accounts"."company_id" = ? [["company_id", 13]]
Role Load (0.2ms) SELECT "roles"."user_id" FROM "roles" WHERE "roles"."id" = ? ORDER BY "roles"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
Update v2
So I'm trying to use this code:
def correct_role
company_user_ids = current_user.companies.map(&:user_ids)
role_user = Role.where(:id => params[:id]).select('user_id').first
unless role_user.user_id.in?(company_user_ids)
redirect_to(errors_path)
end
end
however it redirects to errors_path in any case, this is what I have in console:
Parameters: {"id"=>"1"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Company Load (0.5ms) SELECT "companies".* FROM "companies" INNER JOIN "accounts" ON "companies"."id" = "accounts"."company_id" WHERE "accounts"."user_id" = ? [["user_id", 1]]
(0.4ms) SELECT "users".id FROM "users" INNER JOIN "accounts" ON "users"."id" = "accounts"."user_id" WHERE "accounts"."company_id" = ? [["company_id", 13]]
Role Load (0.3ms) SELECT "roles"."user_id" FROM "roles" WHERE "roles"."id" = ? ORDER BY "roles"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
It seems that a better logic would be to define on the model for the current_user an association through companies and users to roles.
Then you can check:
def correct_role
redirect_to(errors_path) unless current_user.company_user_roles.where(id: params[:id]).exists?
end
It would be a single SQL statement that would return zero or one rows, and execute very quickly with the appropriate indexes in place.
Edit:
To company.rb, add:
has_many :user_roles, through: :users, source: :roles
To user.rb (assuming that current_user is an instance of this model) add:
has_many :company_user_roles, through: :companies, source: :user_roles
Maybe you could do like this:
def correct_role
unless current_user.companies.count > 0 && current_user.role_ids.include?(params[:id])
redirect_to(errors_path)
end
end
User:
class User < ActiveRecord::Base
attr_accessible :email, :username, :password, :password_confirmation, :remember_me
has_many :tasks_users, :dependent => :destroy, :conditions => {:is_owner => true}
has_many :tasks, :through => :tasks_users, :source => :task
Task:
class Task < ActiveRecord::Base
include RankedModel
ranks :sort_order
acts_as_taggable
has_many :tasks_users
has_many :users, :through => :tasks_users
TasksUser:
class TasksUser < ActiveRecord::Base
attr_accessible :is_owner
belongs_to :user
belongs_to :task
validates_uniqueness_of :user_id, :scope => [:user_id, :task_id]
end
They key here is dependent destroy.
Whenever I try to destroy my user, which should destroy the join model, I end up getting this odd sql error:
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 17]]
(0.1ms) begin transaction
ActsAsTaggableOn::Tagging Load (0.1ms) SELECT "taggings".* FROM "taggings" WHERE "taggings"."tagger_id" = 17 AND "taggings"."tagger_type" = 'User'
TasksUser Load (0.1ms) SELECT "tasks_users".* FROM "tasks_users" WHERE "tasks_users"."user_id" = 17 AND "tasks_users"."is_owner" = 't'
Could not log "sql.active_record" event. NoMethodError: undefined method `name' for nil:NilClass
**SQLite3::SQLException: no such column: tasks_users.: DELETE FROM "tasks_users" WHERE "tasks_users"."" = ?**
(0.1ms) rollback transaction
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: tasks_users.: DELETE FROM "tasks_users" WHERE "tasks_users"."" = ?
from /home/steveq/.rvm/gems/ruby-1.9.3-p194#rails32/gems/sqlite3-1.3.7/lib/sqlite3/database.rb:91:in `initialize'
The line with the double asterisk is the line in question - it appears to be searching for a record in "tasks_users"."".
If all I do is change :conditions => {:is_owner => false}, the sql executes without a problem:
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 17]]
(0.1ms) begin transaction
ActsAsTaggableOn::Tagging Load (0.2ms) SELECT "taggings".* FROM "taggings" WHERE "taggings"."tagger_id" = 17 AND "taggings"."tagger_type" = 'User'
TasksUser Load (0.1ms) SELECT "tasks_users".* FROM "tasks_users" WHERE "tasks_users"."user_id" = 17 AND "tasks_users"."is_owner" = 'f'
List Load (0.2ms) SELECT "lists".* FROM "lists" WHERE "lists"."owner_id" = 17
Relationship Load (0.1ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."user_id" = 17
SQL (0.3ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 17]]
(299.4ms) commit transaction
Any ideas about what's happening here, and why having the condition of is_owner change from true to false allows the query and delete statement to execute?
Thanks
Ugh - Hopefully I can save someone else from banging their heads against this wall for an hour.
Problem was how I specified the :conditions.
It needed to be put in double quotes:
has_many :tasks_users, :dependent => :destroy, :conditions => "is_owner = 'true'"
That was it.