I am struggling to authorize an index for a model that doesn't have a direct relationship with my User model. Actually, I am struggling to wrap my head around the idea of Pundit scopes.
I understand that I can't authorize #sites within my SitePolicy, and from all the reading I have done, I believe I just need to do it in a Scope within the SitePolicy Class, but am unsure how to do this.
Here's what I have:
Models
User
has_one :business
has_many :locations, :through => :business
end
Business
belongs_to :user
has_many :locations
end
Location
extend FriendlyId
belongs_to :business
has_one :user, :through => :business
has_many :sites, dependent: :destroy
friendly_id :custom_url, use: :slugged
end
Site
belongs_to :location
end
routes.rb
resources :locations do
resources :sites
end
sites_controller.rb
class SitesController < ApplicationController
before_action :set_site, only: [:show, :edit, :update, :destroy]
before_action :set_location, only: [:new, :show, :edit, :index, :update, :destroy]
def index
#sites = #location.sites.all
authorize Site
end
private
def set_site
#site = Site.find(params[:id])
end
def set_location
#location = Location.friendly.find(params[:location_id])
end
def site_params
params.require(:site).permit(:location_id, :site, :url, :review_site_id, :number_of_reviews, :average_rating, :extra_data)
end
end
site_policy.rb
class SitePolicy < ApplicationPolicy
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.has_role? :admin
scope.all
else
scope.where(scope.location.user == user)
end
end
end
def index?
return true if user.present? and user.has_role? :admin
end
...
schema.rb
ActiveRecord::Schema.define(version: 2018_05_28_085645) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "businesses", force: :cascade do |t|
t.string "name"
t.text "description"
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "more_than_one_location"
t.boolean "signed_up"
t.string "slug"
t.index ["more_than_one_location"], name: "index_businesses_on_more_than_one_location"
t.index ["user_id"], name: "index_businesses_on_user_id"
end
create_table "friendly_id_slugs", force: :cascade do |t|
t.string "slug", null: false
t.integer "sluggable_id", null: false
t.string "sluggable_type", limit: 50
t.string "scope"
t.datetime "created_at"
t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true
t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type"
t.index ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id"
t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type"
end
create_table "locations", force: :cascade do |t|
t.string "location_name"
t.string "address_line_1"
t.string "address_line_2"
t.string "city"
t.string "region"
t.string "country"
t.string "postal_code"
t.string "website"
t.string "phone_number"
t.string "location_contact_email"
t.bigint "business_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "slug", null: false
t.string "custom_url", null: false
t.index ["business_id"], name: "index_locations_on_business_id"
t.index ["custom_url"], name: "index_locations_on_custom_url", unique: true
t.index ["slug"], name: "index_locations_on_slug", unique: true
end
create_table "roles", force: :cascade do |t|
t.string "name"
t.string "resource_type"
t.bigint "resource_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
t.index ["resource_type", "resource_id"], name: "index_roles_on_resource_type_and_resource_id"
end
create_table "sites", force: :cascade do |t|
t.bigint "location_id"
t.string "site"
t.string "url"
t.string "review_site_id"
t.integer "number_of_reviews"
t.decimal "average_rating"
t.jsonb "extra_data", default: {}, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["extra_data"], name: "index_sites_on_extra_data", using: :gin
t.index ["location_id"], name: "index_sites_on_location_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
create_table "users_roles", id: false, force: :cascade do |t|
t.bigint "user_id"
t.bigint "role_id"
t.index ["role_id"], name: "index_users_roles_on_role_id"
t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id"
t.index ["user_id"], name: "index_users_roles_on_user_id"
end
add_foreign_key "businesses", "users"
add_foreign_key "locations", "businesses"
add_foreign_key "sites", "locations"
end
To authorize collections of records, you normally do this with a scope rather than authorizing the action itself. In other words, instead of this:
def index
#sites = #location.sites.all
authorize Site
end
You need to do this:
def index
#sites = policy_scope(#location.sites)
end
Since the link between a site and the user goes through several models, you unfortunately need to JOIN all the way back to the user in order to perform this query in SQL:
class SitePolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.has_role? :admin
scope.all
else
scope.joins(location: :business)
.where(locations: {businesses: {user: user}})
end
end
end
end
Following this design pattern is why Pundit recommends adding the following code to your application:
class ApplicationController < ActionController::Base
include Pundit
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
end
Related
I am attempting to use the trix/actiontext feature of rails 7 to upload images to be included in my blog. I am planning to store them in AWS-S3 bucket, but the problem also happens when trying to store on local disk. Trix works normally when just doing text content with no errors. When I click the "paperclip" icon to attach a file, I get prompted to select a file, and it appears that I am able to, however, there are errors in the console and when I click submit, the file does not save.
This is the error in the console as soon as I choose an image to attach:
POST http://localhost:3000/rails/active_storage/direct_uploads 422
(Unprocessable Entity) actiontext.js:543
Uncaught Error: Direct upload failed: Error creating Blob for
"Screenshot from 2022-10-13 12-38-57.png". Status: 422
at AttachmentUpload.directUploadDidComplete (actiontext.js:849:13)
at BlobRecord2.callback (actiontext.js:618:13)
at BlobRecord2.requestDidError (actiontext.js:560:12)
at BlobRecord2.requestDidLoad (actiontext.js:556:14)
at XMLHttpRequest. (actiontext.js:527:56)
Here is the error from the rails server:
Started POST "/rails/active_storage/direct_uploads" for ::1 at
2022-11-03 14:20:10 -0500 14:20:10 web.1 | Processing by
ActiveStorage::DirectUploadsController#create as JSON 14:20:10 web.1
| Parameters: {"blob"=>{"filename"=>"Screenshot from 2022-10-26
15-49-39.png", "content_type"=>"image/png", "byte_size"=>336871,
"checksum"=>"TZneH7Z7DdCSftEvona6zg=="},
"direct_upload"=>{"blob"=>{"filename"=>"Screenshot from 2022-10-26
15-49-39.png", "content_type"=>"image/png", "byte_size"=>336871,
"checksum"=>"TZneH7Z7DdCSftEvona6zg=="}}} 14:20:10 web.1
Completed 422 Unprocessable Entity in 33ms (ActiveRecord: 8.4ms |
Allocations: 14851)
ActiveRecord::RecordInvalid (Validation failed: Service name can't be
blank): 14:20:10
There is of course a much longer stack trace, but that appears to be the important part. I'm happy to provide the rest if anyone wants it.
When I click submit on the form, I get my normal toast message saying blog is successfully updated, but the image file is not added to the post. I've been searching for the answer to this problem for 3 days and I'm starting to go around in circles. I know that service_name is a column in the actives_storage_blobs database table, but I don't know where it pulls service name from or how to get it there. I'm thinking it's from the service in storage.yml, and it is present there.
storage.yml:
local:
service: Disk
root: <%= Rails.root.join("storage") %>
I'm running out of things to try and search for this problem. I have disabled turbo for the blogs page as I had found a github discussion relating to that, but it hasn't helped. I have read through the guides for active storage and action text multiple times and have not found anything that helped. I would greatly appreciate any suggestions. Thank you.
In case it helps:
Here is the relevant part of the form at app/views/blogs/_form.html.erb:
<div class="form-group">
<%= form.rich_text_area :body, class: 'form-control', rows: 15, placeholder: 'Content' %>
</div>
app/controllers/blogs_controller.rb:
class BlogsController < CommentsController
before_action :set_blog, only: %i[ show edit update destroy toggle_status ]
before_action :set_sidebar_topics, except: [:update, :create, :destroy, :toggle_status]
layout "blog"
access all: [:show, :index], user: { except: [:destroy, :new, :create, :update, :edit, :toggle_status] }, admin: :all, testing: { except: [:destroy, :create, :update]}
def index
if logged_in?(:admin) || logged_in?(:testing)
#blogs = Blog.recent.with_rich_text_body_and_embeds.page(params[:page]).per(5)
else
#blogs = Blog.published.recent.with_rich_text_body_and_embeds.page(params[:page]).per(5)
end
#page_title = "My Portfolio Blog"
end
def show
if logged_in?(:admin) || logged_in?(:testing) || #blog.published?
#blog = Blog.includes(:comments).friendly.find(params[:id])
#comment = Comment.new
#page_title = #blog.title
#seo_keywords = #blog.body
else redirect_to blogs_path, notice: 'You are not authorized to access this page.'
end
end
def new
#blog = Blog.new
end
def edit
end
def create
#blog = Blog.new(blog_params)
respond_to do |format|
if #blog.save
format.html { redirect_to blog_url(#blog), success: "Blog was successfully created." }
else
flash[:danger] = "Blog must have title, body and topic."
format.html { render :new, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #blog.update(blog_params)
format.html { redirect_to blog_url(#blog), success: "Blog was successfully updated." }
else
format.html { render :edit, status: :unprocessable_entity }
end
end
end
def destroy
#blog.destroy
respond_to do |format|
format.html { redirect_to blogs_url, status: :see_other }
end
end
def toggle_status
if #blog.draft? && logged_in?(:admin)
#blog.published!
elsif #blog.published? && logged_in?(:admin)
#blog.draft!
end
redirect_to blogs_url, success: 'Post status has been updated.'
end
private
def set_blog
#blog = Blog.friendly.find(params[:id])
end
def blog_params
params.require(:blog).permit(:title, :body, :topic_id, :status, images: [])
end
def set_sidebar_topics
#side_bar_topics = Topic.with_blogs
end
end
app/models/blog.rb:
class Blog < ApplicationRecord
enum status: { draft: 0, published: 1 }
extend FriendlyId
friendly_id :title, use: :slugged
validates_presence_of :title, :body, :topic_id
has_rich_text :body
has_many_attached :images, dependent: :destroy
has_many :comments, as: :commentable, dependent: :destroy, counter_cache: :commentable_count
belongs_to :topic, optional: true
def self.special_blogs
all
end
def self.featured_blogs
limit(2)
end
def self.recent
order("updated_at DESC")
end
end
ActiveRecord::Schema[7.0].define(version: 2022_10_14_225319) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "action_text_rich_texts", force: :cascade do |t|
t.string "name", null: false
t.text "body"
t.string "record_type", null: false
t.bigint "record_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
end
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
create_table "active_storage_blobs", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.string "service_name", null: false
t.bigint "byte_size", null: false
t.string "checksum"
t.datetime "created_at", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
create_table "active_storage_variant_records", force: :cascade do |t|
t.bigint "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
create_table "blogs", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "slug"
t.integer "status", default: 0
t.bigint "topic_id"
t.integer "commentable_count"
t.text "body"
t.index ["slug"], name: "index_blogs_on_slug", unique: true
t.index ["topic_id"], name: "index_blogs_on_topic_id"
end
create_table "comments", force: :cascade do |t|
t.bigint "user_id", null: false
t.string "commentable_type", null: false
t.bigint "commentable_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "body"
t.index ["commentable_type", "commentable_id"], name: "index_comments_on_commentable"
t.index ["user_id"], name: "index_comments_on_user_id"
end
create_table "friendly_id_slugs", force: :cascade do |t|
t.string "slug", null: false
t.integer "sluggable_id", null: false
t.string "sluggable_type", limit: 50
t.string "scope"
t.datetime "created_at"
t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true
t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type"
t.index ["sluggable_type", "sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_type_and_sluggable_id"
end
create_table "portfolios", force: :cascade do |t|
t.string "title"
t.string "subtitle"
t.text "body"
t.text "main_image"
t.text "thumb_image"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "position"
end
create_table "skills", force: :cascade do |t|
t.string "title"
t.integer "percent_utilized"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "badge"
end
create_table "technologies", force: :cascade do |t|
t.string "name"
t.bigint "portfolio_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["portfolio_id"], name: "index_technologies_on_portfolio_id"
end
create_table "topics", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "name"
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "roles"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "blogs", "topics"
add_foreign_key "comments", "users"
add_foreign_key "technologies", "portfolios"
end
I am going to stop this question. It appears that rails needed updating. I did rails app:update and there were updates related to actiontext and activestorage and the DB schema was updated for those 3 relevant tables. I have done an upload through action text and it has worked. It's not displaying properly but there is data in my storage folder. I am going to go through everything as it is now to see if I can get the image to display properly and will ask a new question if needed. Thank you very much for helping.
Edit to add: Updating rails did fix the uploads completely. The update changed the image processor from mini-magick to vips. Adding this line to application.rb switched it back and then everything worked properly.
config.active_storage.variant_processor = :mini_magick
I want that if a user has ordered a meal, he can talk to the meal provider... and vice versa...
Users can provide meals and order meals too, like on air bnb you can be a guest or a host (or both)
Exemples:
Lets say we have Bob(buyer) who orders a meal from John(maker) I want that John and Bob can chat together
John(buyer) orders a meal from Mike(maker), John and Mike can chat together.
conversations/index.html.erb
<% #users.each do |user| %>
<%= link_to user.full_name , conversations_path(user_id: user), remote: true, method: :post %>
<% end %>
user.rb (I have doubt in this model...)
has_many :meals #Meals that the user offers
has_many :received_orders, class_name: "Order" #The user receive an order
has_many :placed_orders, through: :meals, class_name: "Order" #The user order a meal
has_many :prepared_orders, through: :received_orders, class_name: "Meal", source: :meal #The user has prepared the order
####maybe this way below....TODO
# has_many :meals
# has_many :orders_as_a_customer, class_name: "Order"# same as: has_many :orders
# has_many :orders_as_a_seller, through: :orders_as_a_customer, class_name: "Meal", source: :meal
# has_many :orders, through: :meals
order.rb
class Order < ApplicationRecord
before_save :calculate_price
belongs_to :user
belongs_to :meal
has_many :notifications, as: :topic
monetize :amount_cents, as: :amount
def payment
self.payment_status = true
self.save
end
def calculate_price
self.amount = (self.quantity * meal.price)
end
end
Well I've followed this tutorial to create my chat, and I bet it's possible to adjust to my app to it...
conversation.rb
class Conversation < ApplicationRecord
has_many :messages, dependent: :destroy
belongs_to :sender, foreign_key: :sender_id, class_name: "User"
belongs_to :recipient, foreign_key: :recipient_id, class_name: "User"
validates :sender_id, uniqueness: { scope: :recipient_id }
scope :between, -> (sender_id, recipient_id) do
where(sender_id: sender_id, recipient_id: recipient_id).or(
where(sender_id: recipient_id, recipient_id: sender_id)
)
end
def self.get(sender_id, recipient_id)
conversation = between(sender_id, recipient_id).first
return conversation if conversation.present?
create(sender_id: sender_id, recipient_id: recipient_id)
end
def opposed_user(user)
user == recipient ? sender : recipient
end
end
conversations_controller.rb
class ConversationsController < ApplicationController
def create
#conversation = Conversation.get(current_user.id, params[:user_id])
add_to_conversations unless conversated?
respond_to do |format|
format.js
end
end
def index
session[:conversations] ||= []
#users = User.all.where.not(id: current_user)
#conversations = Conversation.includes(:recipient, :messages).find(session[:conversations])
end
def close
#conversation = Conversation.find(params[:id])
session[:conversations].delete(#conversation.id)
respond_to do |format|
format.js
end
end
private
def add_to_conversations
session[:conversations] ||= []
session[:conversations] << #conversation.id
end
def conversated?
session[:conversations].include?(#conversation.id)
end
end
my actual schema.rb
ActiveRecord::Schema.define(version: 20170629192651) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "attachinary_files", force: :cascade do |t|
t.string "attachinariable_type"
t.integer "attachinariable_id"
t.string "scope"
t.string "public_id"
t.string "version"
t.integer "width"
t.integer "height"
t.string "format"
t.string "resource_type"
t.datetime "created_at"
t.datetime "updated_at"
t.index ["attachinariable_type", "attachinariable_id", "scope"], name: "by_scoped_parent", using: :btree
end
create_table "categories", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "conversations", force: :cascade do |t|
t.integer "recipient_id"
t.integer "sender_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "order_id"
t.index ["order_id"], name: "index_conversations_on_order_id", using: :btree
t.index ["recipient_id", "sender_id"], name: "index_conversations_on_recipient_id_and_sender_id", unique: true, using: :btree
end
create_table "ingredients", force: :cascade do |t|
t.string "name"
t.integer "meal_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["meal_id"], name: "index_ingredients_on_meal_id", using: :btree
end
create_table "meals", force: :cascade do |t|
t.string "menu_name"
t.integer "portion"
t.date "availability"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "category_id"
t.string "images"
t.string "location"
t.float "latitude"
t.float "longitude"
t.integer "price"
t.index ["user_id"], name: "index_meals_on_user_id", using: :btree
end
create_table "messages", force: :cascade do |t|
t.text "body"
t.integer "user_id"
t.integer "conversation_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["conversation_id"], name: "index_messages_on_conversation_id", using: :btree
t.index ["user_id"], name: "index_messages_on_user_id", using: :btree
end
create_table "notifications", force: :cascade do |t|
t.boolean "read", default: false
t.string "content"
t.integer "user_id"
t.string "topic_type"
t.integer "topic_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "order_id"
t.index ["order_id"], name: "index_notifications_on_order_id", using: :btree
t.index ["topic_type", "topic_id"], name: "index_notifications_on_topic_type_and_topic_id", using: :btree
t.index ["user_id"], name: "index_notifications_on_user_id", using: :btree
end
create_table "orders", force: :cascade do |t|
t.text "message"
t.boolean "payment_status", default: false
t.integer "quantity"
t.integer "user_id"
t.integer "meal_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "amount_cents", default: 0, null: false
t.json "payment"
t.index ["meal_id"], name: "index_orders_on_meal_id", using: :btree
t.index ["user_id"], name: "index_orders_on_user_id", using: :btree
end
create_table "reviews", force: :cascade do |t|
t.integer "rating"
t.text "comment"
t.integer "meal_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["meal_id"], name: "index_reviews_on_meal_id", using: :btree
t.index ["user_id"], name: "index_reviews_on_user_id", using: :btree
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "provider"
t.string "uid"
t.string "facebook_picture_url"
t.string "first_name"
t.string "last_name"
t.string "token"
t.datetime "token_expiry"
t.string "nickname"
t.string "avatar"
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
end
add_foreign_key "conversations", "orders"
add_foreign_key "meals", "users"
add_foreign_key "messages", "conversations"
add_foreign_key "messages", "users"
add_foreign_key "notifications", "orders"
add_foreign_key "notifications", "users"
add_foreign_key "orders", "meals"
add_foreign_key "orders", "users"
add_foreign_key "reviews", "meals"
add_foreign_key "reviews", "users"
end
Concluding from the comments, you'd need to have different models. Here are the ones that make sense to me:
class User < ApplicationRecord
has_many :meals
has_many :orders
has_many :sent_messages, :class => 'ChatMessage', :foreign_key => 'sender_id'
has_many :received_messages, :class => 'ChatMessage', :foreign_key => 'receiver_id'
end
class Meal < ApplicationRecord
belongs_to :user
belongs_to :order_item
end
class Order < ApplicationRecord
belongs_to :user
has_many :order_items
end
class OrderItem < ApplicationRecord
belongs_to :order
has_one :meal
end
class ChatMessage < ApplicationRecord
belongs_to :sender, :class => 'User'
belongs_to :receiver, :class => 'User'
end
[question solved] => I just made a silly mistake and forgot to add #match.save at the end of my method :)
I'm relatively new to RoR and the ActiveRecord architecture. I'm trying to build a simple app that can match 2 users together based on their interests. First here's the schema of my database:
Link to my DB's schema
or if you prefer here's the schema.rb file:
ActiveRecord::Schema.define(version: 20160612080318) do
create_table "conversations", force: :cascade do |t|
t.integer "user1_id"
t.integer "user2_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "conversations", ["user1_id"], name: "index_conversations_on_user1_id", using: :btree
add_index "conversations", ["user2_id"], name: "index_conversations_on_user2_id", using: :btree
create_table "interests", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "matches", force: :cascade do |t|
t.integer "user1_id"
t.integer "user2_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "matches", ["user1_id"], name: "index_matches_on_user1_id", using: :btree
add_index "matches", ["user2_id"], name: "index_matches_on_user2_id", using: :btree
create_table "messages", force: :cascade do |t|
t.text "content"
t.integer "conversation_id"
t.integer "user_id"
t.datetime "read_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "messages", ["conversation_id"], name: "index_messages_on_conversation_id", using: :btree
add_index "messages", ["user_id"], name: "index_messages_on_user_id", using: :btree
create_table "user_interests", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.integer "interest_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "first_name"
t.string "last_name"
t.integer "age"
t.string "avatar_url"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_foreign_key "messages", "conversations"
add_foreign_key "messages", "users"
add_foreign_key "user_interests", "interests"
add_foreign_key "user_interests", "users"
end
Just to test my models I didn't take into account the algorithm that will match users based on their interest. So here's my matches_controller.rb:
class MatchesController < ApplicationController
def new
#match = Match.new
end
def create
#match = Match.new(match_params)
#match.user1_id = current_user.id
#match.user2_id = User.first.id
end
private
def match_params
params.require(:match).permit(:user1_id, :user2_id)
end
end
my match model:
class Match < ActiveRecord::Base
belongs_to :user1, class_name: "User"
belongs_to :user2, class_name: "User"
end
and finally my user model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :user_interests, dependent: :destroy
has_many :interests, through: :user_interests, dependent: :destroy
has_many :messages, dependent: :destroy
has_many :matches, dependent: :destroy
def conversations
Conversation.includes(:messages)
.where("user1_id = :id OR user2_id = :id", id: id)
.order("messages.created_at DESC")
end
def other_user(conversation)
conversation.users.include?(self) ? conversation.other_user(self) : nil
end
def unread_conversations
conversations.select { |c| c.unread_messages?(self) }
end
def unread_conversations_count
unread_conversations.count
end
def unread_conversations?
unread_conversations_count > 0
end
def one_avatar_url
avatar_url ? avatar_url : "http://placehold.it/64x64"
end
end
I've tried to test my Match.create method in the console but got only nil for user1_id and user2_id. I've been looking all day long everywhere without being able to pinpoint what's wrong with my code.
Thanks a lot for helping me on this issue :)
I am writing a Rails 4.2 app with models user, notecard, tag and tagging (for the m-2-m relationship).
A tag can have multiple notecards and a notecard can have multiple tags.
A card belongs to a user and a tag DOESN'T belong to a user.
How can I scope the tags that only a user has used?
I want to have an index of all tags and an index of the tags a user has actually used.
Thanks!
Here is the schema, as I don't have an idea on how to implement the where clause to index the tags a user has used.
to give you an idea, I'm looking for something like this
def index_of_used_tags
#Take all tags, return those that have cards from this user
end
class User < ActiveRecord::Base
has_many :comments
has_many :folders
has_many :cards
end
class Tag < ActiveRecord::Base
has_many :taggings
has_many :cards, through: :taggings
validates_presence_of :name
validates_uniqueness_of :name
end
class Folder < ActiveRecord::Base
belongs_to :user
validates_presence_of :name
validates_uniqueness_of :name, scope: :user_id
end
class Card < ActiveRecord::Base
belongs_to :user
belongs_to :folder
has_many :taggings
has_many :tags, through: :taggins
end
ActiveRecord::Schema.define(version: 20150604113358) do
enable_extension "plpgsql"
create_table "cards", force: :cascade do |t|
t.string "object"
t.text "content"
t.string "source"
t.integer "user_id"
t.integer "folder_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "cards", ["folder_id"], name: "index_cards_on_folder_id", using: :btree
add_index "cards", ["user_id"], name: "index_cards_on_user_id", using: :btree
create_table "comments", force: :cascade do |t|
t.text "content"
t.string "commentable_type"
t.integer "commentable_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree
create_table "folders", force: :cascade do |t|
t.string "name"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "folders", ["user_id"], name: "index_folders_on_user_id", using: :btree
create_table "taggings", force: :cascade do |t|
t.integer "card_id"
t.integer "tag_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "taggings", ["card_id"], name: "index_taggings_on_card_id", using: :btree
add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree
create_table "tags", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "fname"
t.string "lname"
t.boolean "admin", default: false
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_foreign_key "cards", "folders"
add_foreign_key "cards", "users"
add_foreign_key "comments", "users"
add_foreign_key "folders", "users"
add_foreign_key "taggings", "cards"
add_foreign_key "taggings", "tags" end
You can set up a has_many through relationship between User and Tag
class User < ActiveRecord::Base
has_many :comments
has_many :folders
has_many :cards
has_many :tags, through: :cards
end
Then user.tags would give you all the tags the user has used.
User.includes(:cards => :taggings).where('users.id = ?', current_user.id)
Try this query
recently I make some modification to my app
I use two functions with 1 user data base, I don't know if I make some mistakes routing or migrating something.
My problem is I can't update attributes of a user, when I try to update I get redirect to main page, and when I try to login says that my account don't exist.
Here my schema
ActiveRecord::Schema.define(version: 20150510044605) do
create_table "comments", force: :cascade do |t|
t.integer "link_id"
t.text "body"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "comments", ["link_id"], name: "index_comments_on_link_id"
add_index "comments", ["user_id"], name: "index_comments_on_user_id"
create_table "links", force: :cascade do |t|
t.string "title"
t.string "url"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
end
add_index "links", ["user_id"], name: "index_links_on_user_id"
create_table "pins", force: :cascade do |t|
t.string "title"
t.text "description"
t.integer "price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
t.integer "user_id"
end
add_index "pins", ["user_id"], name: "index_pins_on_user_id"
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
create_table "votes", force: :cascade do |t|
t.integer "votable_id"
t.string "votable_type"
t.integer "voter_id"
t.string "voter_type"
t.boolean "vote_flag"
t.string "vote_scope"
t.integer "vote_weight"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "votes", ["votable_id", "votable_type", "vote_scope"], name: "index_votes_on_votable_id_and_votable_type_and_vote_scope"
add_index "votes", ["voter_id", "voter_type", "vote_scope"], name: "index_votes_on_voter_id_and_voter_type_and_vote_scope"
end
App Controller
lass ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |params|
params.permit(
:email, :password, :password_confirmation, :name,
:last_name, :profile_name
)
}
devise_parameter_sanitizer.for(:account_update) { |params|
params.permit(
:email, :password, :password_confirmation, :name,
:last_name, :profile_name
)
}
end
end
And routes
Rails.application.routes.draw do
resources :comments
resources :pins
devise_for :users
resources :links do
member do
put "like", to: "links#upvote"
put "dislike", to: "links#downvote"
end
resources :comments
end
resources :pins do
member do
put "like", to: "pins#upvote"
end
end
authenticated :user do
root 'links#index', as: "authenticated_root"
end
root 'pages#home'
end
I'm new in rails, so I don't know how or what to find.