I have an Order and an OrderTransactions model in my Rails4 application. They have a basic has_one and belongs_to relationship between them.
I'm posting requests from /orders/new page to the bank's URL as you can see below:
<%= simple_form_for(#order, :url => "https://testsanalpos.est.com.tr/servlet/est3Dgate", :method => :post) do |f| %>
<% #hashing.each do |k, v| %>
<%= f.input k, input_html: {name: k, value: v}, as: :hidden %>
<% end %>
<%= f.input :participation_id, ... %>
<%= f.button :submit, "Ödeme Yap" %>
<% end %>
The #hashing, hash is coming from my controller =>
class OrdersController < ApplicationController
before_filter :authenticate_user!
before_action :set_order, only: [:show, :edit, :update, :destroy]
skip_before_action :verify_authenticity_token
def new
#order = Order.new
#hashing = {
clientid: POS['clientid'],
oid: Time.now.to_i.to_s,
amount: POS['amount'],
okUrl: POS['okUrl'],
failUrl: POS['failUrl'],
rnd: Time.now.to_i.to_s,
}
end
def create
#order = Order.new(order_params)
respond_to do |format|
#order.purchase
end
end
def success
end
def fail
end
private
def set_order
#order = Order.find(params[:id])
end
def order_params
params.require(:order).permit(:id, :ip_address, :first_name, :last_name, :card_brand, :card_number, :card_verification, :card_expires_on, :user_id, :participation_id)
end
end
Order.rb =>
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :participation
has_one :transaction, :class_name => "OrderTransaction"
def purchase
participation.update_attribute(:payment_status, true)
create_transaction(:action => "purchase", :response => response)
end
end
The bank's page is getting all necessary information from the user like credit card number, card expiration date etc. My application is not doing anything about purchase process, all of them are happening on the bank's side.
Then the bank is returning me a bunch of parameters about payment process. If the payment is success full, bank is posting the parameters to my /orders/success.html.erb and if it fails it is posting to /order/fail.html.erb.
I have 2 problems =>
1) I want the Order model instance is created whatever the response is successful or failed. It seems like that should be happening by create method in controller but it not working :/
2) How can I get the parameters that the bank send to my /fail or /success URL? I need to get them into my application and save them as a OrderTransaction instance in my database. I can see the parameters in my logs like this =>
Started POST "/orders/fail" for 127.0.0.1 at 2014-06-01 13:40:28 +0300
Processing by OrdersController#fail as HTML
Parameters:
{
"TRANID"=>"",
"Faturafirma"=>"OMÜ Uzaktan Eğitim Merkezi",
"islemtipi"=>"Auth",
"refreshtime"=>"5",
"lang"=>"tr",
"amount"=>"30",
"ACQBIN"=>"490740",
"clientIp"=>"193.140.28.145",
"name"=>"AKBANK",
"cardHolderName"=>"dsadas dasdsa",
"okUrl"=>"http://localhost:3000/orders/success",
"storetype"=>"3d_pay_hosting",
"Response"=>"Declined"
....
}
DB Schema =>
create_table "orders", force: true do |t|
t.integer "participation_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "order_transactions", force: true do |t|
t.integer "order_id"
t.string "clientip"
t.string "cardholdername"
t.string "response"
t.string "errmsg"
...
t.datetime "created_at"
t.datetime "updated_at"
end
Routes.rb =>
...
post 'orders/success' => 'orders#success'
post 'orders/fail' => 'orders#fail'
resources :orders, only: [:index, :new, :create]
...
Suggest:
Move your creation success failure into a model and pass it such.
def success
Order.create_from_params(order_params)
end
def fail
Order.create_from_params(order_params)
end
Then handle success failure from params using response Decline etc..
class Order<ActivewRecord::Base
def self.create_from_params(order_params)
self.create(inflatewithfields) && self.purchase if params[response'] == 'success'
self.create(inflatewithfields) if params[response'] == 'Decline'
end
end
Related
Im making an application where an user can book a hour of training. I want to give the app the restriction of when a training has 24 users booked nobody cant book anymore (at least some user delete his book), my question is how can i implement this function MAXSLOT in training model and how i can put it to work
class BookingsController < ApplicationController
before_action :load_training, only: [:create]
def new
#booking = Booking.new
#training = Training.find(params[:training_id])
#booking.training_id
end
def create
#booking = #training.bookings.build(booking_params)
#booking.user = current_user
if #booking.save
flash[:success] = "Book created"
redirect_to trainings_path
else
render 'new'
end
end
def index
#bookings = Booking.all
end
def destroy
#booking = Booking.find(params[:id])
#booking.destroy
flash[:success] = "Book deleted"
redirect_to trainings_path
end
private
def booking_params
params.require(:booking).permit(:user_id, :training_id)
end
def load_training
#training = Training.find(params[:training_id])
end
end
Booking model:
class Booking < ApplicationRecord
belongs_to :user
belongs_to :training
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :training_id, presence: true
end
My routes.rb:
Rails.application.routes.draw do
root 'static_pages#home'
get '/signup', to: 'users#new'
get '/contact', to: 'static_pages#contact'
get '/about', to: 'static_pages#about'
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
resources :account_activations, only: [:edit]
resources :password_resets, only: [:new, :create, :edit, :update]
resources :trainings do
resources :bookings
end
resources :users
end
Training model:
class Training < ApplicationRecord
has_many :users, through: :bookings
has_many :bookings
end
Trainings controller:
class TrainingsController < ApplicationController
def show
#training = Training.find(params[:id])
end
def index
#trainings = Training.all
end
end
Index of training view:
<h1>Hours</h1>
<ul class="trainings">
<% #trainings.each do |training| %>
<li>
<%= link_to training.hour, training_path(training) %>
</li>
<% end %>
</ul>
Show of training view:
<div class="row">
<section>
<h1>
HOUR: <%= #training.hour %>
</h1>
</section>
<section>
<h1>
SLOTS: <%= #training.slots %>
</h1>
</section>
<center>
<%= render 'bookings/booking_form' if logged_in? %>
<%= render 'bookings/index_bookings' if logged_in? %>
</center>
This is my squema.rb:
create_table "bookings", force: :cascade do |t|
t.integer "user_id"
t.integer "training_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["training_id"], name: "index_bookings_on_training_id"
t.index ["user_id"], name: "index_bookings_on_user_id"
end
create_table "trainings", force: :cascade do |t|
t.integer "slots"
t.text "hour"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["hour"], name: "index_trainings_on_hour"
end
Thanks
There are a couple of approaches to this. I'd recommend something like this:
# Training
class Training < ApplicationRecord
has_many :users, through: :bookings
has_many :bookings
# Check if anymore bookings can be added
def can_book?
bookings.count < slots # Depending on the exact logic of your app, it might make more sense to use users.count here instead. Your call.
end
end
# Booking
class Booking < ApplicationRecord
belongs_to :user
belongs_to :training
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :training_id, presence: true
# It might make sense to only validate this on create. Get rid of the `#` on the below line if you think so.
validate :training_not_full?#, on: :create
private
def training_not_full?
errors.add(:training, "The training session is full!") unless training.can_book?
end
end
When you get to the if #booking.save in the controller, #booking.valid? is automatically called. If that returns false, then #booking.save will not save the record, and will also return false. This way, you can control persistence logic through your own validations in the models. The controller logic doesn't need to change at all.
I recommend reading about rails validations here. Skip to 6.2 for the relevant section.
Also, as a word of warning, default_scopes usually wind up doing more harm then good. Eventually you'll have a use case where you'll want to order by something else, and you'll find yourself working around the scope pretty often. You'll probably save yourself some headaches down the line if you get rid of it now.
I created a post_category table to add a category to specifics posts.
For example I created post_categories as Countries, Like Japan or China. And I want to create post which are come from culture or mode from countries like Japan or China. I focused only on post_categories as countries for now and below is the code I did.
I created this PostCategory, here are the migration and model
create_table "post_categories", force: :cascade do |t|
t.string "name"
t.string "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
class PostCategory < ActiveRecord::Base
has_many :posts, dependent: :destroy
NAMES = ["Japon", "Chine", "Corée du Sud", "Moyen Orient", "Indien"]
validates :name, inclusion: { in: PostCategory::NAMES, allow_nil: false }
end
And I created a Post with the PostCategory foreign key, here are the migration and model
create_table "posts", force: :cascade do |t|
t.string "cover"
t.string "subtitle"
t.string "title"
t.text "introduction"
t.text "body"
t.text "conclusion"
t.string "tag"
t.string "source"
t.string "link"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "post_category_id"
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :post_category, dependent: :destroy
TAGS = ["Design", "Mode", "Tendance", "Life-Style", "Tradition", "Gastronomie", "Insolite", "Technologie"]
validates :tag, inclusion: { in: Post::TAGS, allow_nil: false }
mount_uploader :cover, ImageUploader
end
I want to create a category with a simple form collection and I want i will be displayed on the post show#view
Here is the post_categories controller
class PostCategoriesController < ApplicationController
# before_action :set_post_category, only: [:show, :new, :create, :destroy]
def show
#post_category = PostCategory.find(params[:id])
end
def index
#post_categories = PostCategory.all
end
def create
#post_category = post_categories.new(post_category_params)
if #post_category.save
redirect_to #post
else
render 'post_categories/show'
end
end
def new
#post_category = PostCategory.new
end
def edit
end
def update
end
def destroy
#post_category = PostCategory.find(params[:id])
#post_category.destroy
redirect_to post_path(#post)
end
private
# def set_post
# #post = Post.find(params[:post_id])
# end
def find_post_category
#post_category = PostCategory.find(params[:id])
end
def post_category_params
params.require(:post_category).permit(:name, :description)
end
end
And here is the posts controller
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 = #post_category.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_post_category
#post_category = PostCategory.find(params[:post_category_id])
end
def post_params
params.require(:post).permit(:title, :subtitle, :introduction, :body, :cover, :tag, :post_category_id)
end
end
I don't know what views could I do create and how calling the post new#view because I configured my routes like that, and I need a post_category_id.
resources :post_categories do
resources :posts
end
That's I have to use this following path
post_category_posts GET /post_categories/:post_category_id/posts(.:format) posts#index
POST /post_categories/:post_category_id/posts(.:format) posts#create
new_post_category_post GET /post_categories/:post_category_id/posts/new(.:format) posts#new
edit_post_category_post GET /post_categories/:post_category_id/posts/:id/edit(.:format) posts#edit
post_category_post GET /post_categories/:post_category_id/posts/:id(.:format) posts#show
PATCH /post_categories/:post_category_id/posts/:id(.:format) posts#update
PUT /post_categories/:post_category_id/posts/:id(.:format) posts#update
DELETE /post_categories/:post_category_id/posts/:id(.:format) posts#destroy
post_categories GET /post_categories(.:format) post_categories#index
POST /post_categories(.:format) post_categories#create
new_post_category GET /post_categories/new(.:format) post_categories#new
edit_post_category GET /post_categories/:id/edit(.:format) post_categories#edit
post_category GET /post_categories/:id(.:format) post_categories#show
PATCH /post_categories/:id(.:format) post_categories#update
PUT /post_categories/:id(.:format) post_categories#update
DELETE /post_categories/:id(.:format) post_categories#destroy
I want to add the category on my show#view post and create a multisearch access to find posts added to a specific category. Thank you for your help
The way you have setup your relations since has a very big flaw - a post can only belong to a single category since the id is stored on the posts table.
Instead you would commonly use a many to many relationship:
class Post < ActiveRecord::Base
has_many :categories, :categories
has_many :post_categories
end
class Category < ActiveRecord::Base
has_many :posts
has_many :posts, through: :categories
end
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
validates_uniqueness_of :category_id, scope: :post_id
end
Here the categorizations table acts as a join table which links Post and Category - A post can have any number of categories and vice versa.
ADDED:
To create the join model and a migration to create table you would do:
rails g model Categorization post:belongs_to category:belongs_to
Using belongs to in the generator by default creates foreign keys for post_id and category_id.
You might want to add a compound uniqueness constraint in the migration as well.
def change
# ...
add_index(:categorizations, [:post_id, :category_id], unique: true)
end
Your on the right track with your CategoriesController but I would not use a nested route to create posts. Instead you might want to just use a plain old rails controller and let the user select categories via check boxes:
<%= form_for(#post) do |f| %>
<%= f.collection_check_boxes :category_ids, Category.all, :id, : name %>
<% end %>
You would then add the category_ids param to the whitelist:
def post_params
params.require(:post)
.permit(:title, :subtitle, :introduction,
:body, :cover, :tag, category_ids: []
)
end
The weird category_ids: [] syntax is because we want to allow an array of scalar values.
Querying for posts from a category by id can be done like so:
Post.joins(:categories).where(category: 1)
You can even select multiple categories by passing an array:
Post.joins(:categories).where(category: [1, 2, 3])
To hook this together from the view you would use a link (for a single category) or a form (yes forms can be used for GET) with checkboxes or selects.
Dealing with textual input is a bit trickier. A naive implementation would be:
Post.joins(:categories).where(category: { name: params[:search_query] })
However the user input would have to match exactly. There are several gems that provide search features to active record. However I would wait with the feature until you have a bit more experience as it can be tricky to implement.
See:
Rails guides: The has_many :through Association
Rails guides: Strong Parameters
Rails api: collection_check_boxes
Ive been trying to build an messaging system for my site which uses devise for authentication. The functionality it requires is to be able to send a message to either one or more recipients (preferably with a checklist form listing users as well). After searching for a while I found a couple gems such as mailboxer, but I didn't need all its features and wanted to build my own system for sake of learning (still a newbie at rails).
I have followed this ancient tutorial ( http://web.archive.org/web/20100823114059/http://www.novawave.net/public/rails_messaging_tutorial.html ). I realize this is a very old tutorial but it is the only one I could find which matched what I was trying to achieve.
I have followed the tutorial to a T and even copied and pasted the code from the tutorial after my code didn't work.
when trying to access http://localhost:3000/mailbox i get a NoMethodError in MailboxController#index
undefined method `messages' for nil:NilClass
app/controllers/mailbox_controller.rb:12:in `show'
app/controllers/mailbox_controller.rb:6:in `index'
I have also referenced this question Rails 3: undefined method messages for Folder which had the same error as me but the topic just seemed to go no where.
mailbox_controller.rb
class MailboxController < ApplicationController
def index
#folder = current_user.inbox
show
render :action => "show"
end
def show
#folder ||= current_user.folders.find_by(params[:id])
#messages = #folder.messages :include => :message, :order => "messages.created_at DESC"
end
end
models/folder.rb
class Folder < ActiveRecord::Base
acts_as_tree
belongs_to :user
has_many :messages, :class_name => "MessageCopy"
end
Any help with this would be awesome, also just let me know if you need any more info and will post it.
I ended up figuring out the messaging system with a few modifications. I wanted to post my whole solution since it gave me a difficult time and might be useful to others. I kept it very simple and did not include the the folder model which was giving me the problem in the first place, but none the less it is functioning.
Heres are my associations
model/message.rb
attr_reader :user_tokens
belongs_to :sender, :class_name => 'User'
has_many :recipients
has_many :users, :through => :recipients
def user_tokens=(ids)
self.user_ids = ids
end
model/recipient.rb
belongs_to :message
belongs_to :user
model/user.rb
has_many :messages, :foreign_key => :sender_id
This is my messages controller
messages_controller.rb
def new
#message = Message.new
#user = current_user.following
#users = User.all
# #friends = User.pluck(:name, :id).sort
end
def create
#message = current_user.messages.build(message_params)
if #message.save
flash[:success] = "Message Sent!"
redirect_to messages_path
else
flash[:notice] = "Oops!"
render 'new'
end
end
def index
#user = User.find(current_user)
#messages = Recipient.where(:user_id => #user).order("created_at DESC")
end
private
def message_params
params.require(:message).permit(:body, :sender_id, user_tokens: [])
end
My Views
_form.html.erb
</div>
<!-- displays the current users frinds their following -->
<%= f.select :user_tokens, #user.collect {|x| [x.name, x.id]}, {}, :multiple => true, class: "form-control" %>
<br>
<div class="modal-footer">
<%= f.button :submit, class: "btn btn-primary" %>
</div>
Schema
messages_table
t.text "body"
t.integer "sender_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "body_html"
recipients_table
t.integer "message_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
I hope this helps.
I'm very new to rails so please be detailed in your responses. I'm building a web app that uses devise for authentication. The part that I'm stuck on right now is a user to user messaging system. The idea is that User A logs into the app and can visit user B's profile, and on User B's profile can click on a link that allows User A to compose a message to User B. Then User B can log into the app and visit the inbox where User A's message will be found.
I believe that I'm having trouble defining the sender and recipient roles here, right now I'm trying to display the form that users will compose their message in. Can anyone see what I'm doing wrong here? I get the following error. I've read that the thing to do is add the User_id field to the table, but I'm hoping to link this messages up using sender_id and recipient_id, which both equal user_id (e.g. User 1[sender] sends a message to User 2 [recipient]):
unknown attribute: user_id
def new
#message = current_user.messages.new recipient_id: params[:sender_id]
end
Additionally, for you rails experts or anyone that has done something similar to this, can you advise whether or not I'm going in the right direction, or offer any guidance? I'm sort of coding blind here and just trying to make it up as I go along. Any guidance would be hugely appreciated and save me a lot of time i'm sure. Code below:
Users Migration
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
t.string :first_name
t.string :last_name
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
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.timestamps
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
end
end
Messages Migration
class CreateMessages < ActiveRecord::Migration
def change
create_table :messages do |t|
t.string :content
t.integer :sender_id
t.integer :recipient_id
t.timestamps
end
end
end
schema.rb
ActiveRecord::Schema.define(version: 20140909174718) do
create_table "messages", force: true do |t|
t.string "content"
t.integer "sender_id"
t.integer "recipient_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", force: true do |t|
t.string "first_name"
t.string "last_name"
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 "current_industry"
t.integer "years_in_current_industry"
t.string "hobbies"
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
end
routes.rb
Catalyst::Application.routes.draw do
devise_for :users, :controllers => { :registrations => "registrations" }
devise_scope :user do
get 'register', to: 'devise/registrations#new'
get 'login', to: 'devise/sessions#new', as: :login
get 'logout', to: 'devise/sessions#destroy', as: :logout
end
resources :users do
member do
get 'edit_profile'
end
resources :messages, only: [:new, :create]
end
resources :messages, only: [:index, :show, :destroy]
root to: "home#index"
match '/about', to: 'static_pages#about', via: 'get'
match '/contact', to: 'static_pages#contact', via: 'get'
match '/help', to: 'static_pages#help', via: 'get'
match '/legal', to: 'static_pages#legal', via: 'get'
end
users_controller
class UsersController < ApplicationController
before_filter :authenticate_user!
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def new
end
def create
end
def edit
end
def update
#user = User.find(params[:id])
#user.update!(user_params)
redirect_to #user
end
def destroy
end
def edit_profile
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation, :current_industry, :years_in_current_industry, :hobbies)
end
def sender
#user = User.find(params[:id])
end
def recipient
#user = User.find(params[:id])
end
end
messages_controller
class MessagesController < ApplicationController
before_action :set_recipient
def new
#message = Message.new
#recipient = User.find(params[:user_id])
end
def create
#message = Message.new message_params
if #message.save
flash[:success] = "Your message has been sent!"
redirect_to user_messages_path
else
flash[:failure] = "Please try again."
redirect_to users_path
end
end
private
def message_params
params.require(:message).permit(:content, :sender_id, :recipient_id)
end
end
user.rb
class User < ActiveRecord::Base
has_many :from_messages, class_name: 'Message', :foreign_key => "sender_id"
has_many :to_messages, class_name: 'Message', :foreign_key => "recipient_id"
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :first_name, :last_name, :email, :password, :password_confirmation, :remember_me, :current_industry, :years_in_current_industry, :hobbies
end
message.rb
class Message < ActiveRecord::Base
belongs_to :sender, class_name: "User"
belongs_to :recipient, class_name: "User"
validates :content, presence: true, length: { maximum: 500 }
validates :sender_id, presence: true
validates :recipient_id, presence: true
end
messages/index.html.erb
<h2>Inbox</h2>
messages/new.html.erb
<h1>Create Message</h1>
<%= form_for [#recipient, #message] do |f| %>
<%= f.hidden_field :recipient_id, value: #recipient.id %>
<%= f.label "Enter your message below" %><br />
<%= f.text_area :content %>
<%= f.submit "Send" %>
<% end %>
rake routes
user_messages POST /users/:user_id/messages(.:format) messages#create
new_user_message GET /users/:user_id/messages/new(.:format) messages#new
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
messages GET /messages(.:format) messages#index
message GET /messages/:id(.:format) messages#show
DELETE /messages/:id(.:format) messages#destroy
Models
#app/models/user.rb
class User < ActiveRecord::Base
has_many :messages, class_name: "Message", foreign_key: "recipient_id"
has_many :sent_messages, class_name: "Message", foreign_key: "sender_id"
end
#app/models/message.rb
class Message < ActiveRecord::Base
belongs_to :recipient, class_name: "User", foreign_key: "recipient_id"
belongs_to :sender, class_name: "User", foreign_key: "sender_id"
scope :unread, -> { where read: false }
end
This should give you the ability to create messages which "belong" to a user (IE the recipient), and then you can associate a "sender" profile to those messages.
--
Controllers
This will give you the ability to call the following:
#app/controllers/messages_controller.rb
class MessagesController < ApplicationController
before_action :set_recipient, only: [:new, :create]
def new
#message = current_user.sent_messages.new
end
def create
#message = current_user.sent_messages.new message_params
#message.recipient_id = #recipient.id
#message.save
end
def index
#messages = current_user.messages
end
def destroy
#message = current_user.messages.destroy params[:id]
end
def show
#message = current_user.messages.find params[:id]
end
private
def message_params
params.require(:message).permit(:content, :recipient_id, :sender_id)
end
def set_recipient
#recipient = User.find params[:user_id]
end
end
--
Routes
#config/routes.rb
devise_for :users, path: "", controllers: { :registrations => "registrations" }, path_names: {sign_up: "register", sign_in: "login", sign_out: "logout"}
resources :users do
get :profile
resources :messages, only: [:new, :create] #-> domain.com/users/:user_id/messages/new
end
resources :messages, only: [:index, :show, :destroy] #-> domain.com/messages/:id
--
Views
This will give you the ability to use the following links:
#app/views/users/show.html.erb (user to send message to)
<%= link_to "Send Message", user_messages_path(#user.id) %>
#app/views/messages/new.html.erb
<%= form_for [#recipient, #user] do |f| %>
<%= f.text_field :content %>
<%= f.submit %>
<% end %>
#app/views/messages/index.html.erb
<h2>Inbox</h2>
<% #messages.each do |message| %>
<%= message.content %>
<% end %>
--
Fix
I've read that the thing to do is add the User_id field to the table,
but I'm hoping to link this messages up using sender_id and
recipient_id, which both equal user_id (e.g. User 1[sender] sends a
message to User 2 [recipient])
You don't need to add user_id to your table. user_id is merely a foreign_key, which you've overridden in your models.
All you need to do is set the recipient_id and sender_id, which we're doing in the create method:
def create
#message = current_user.message.new message_params
#message.recipient_id = #recipient.id
#message.save
end
You've done some very clever things here.
Firstly, you have implicitly set the sender_id foreign key by calling current_user.messages. If you had called Message.new, it would have been a completely different story (having to set sender_id)
Secondly, because you're using nested routes, you'll be able to use the #recipient variable you've set in the before_action method to give us the id for the recipient_id.
This should work for you. You won't need to use inverse_of unless you are trying to access "parent" model data in a child / nested model.
Recommendations
What you're doing is completely valid
The core trick is to make sure your Message model is completely separate & independent to your User. This is achieved with your setup, allowing you to create the various objects that you require.
The other aspect you need to consider is how you're going to ensure you're able to provide the users with the ability to have "threaded" messages. You'll achieve this using one of the hierarchy gems, either Ancestry or Closure_Tree
Adding this functionality will be a little more in-depth. I can provide information if you require (just leave a comment)
Threading
The hierarchy gems are actually relatively simple to use.
The trick to "treading" your messages is to use one of these gems (either Ancestry or Closure_Tree), as they provide you with "methods" which you can call on your items. They work by creating several columns in your database, populating them as you save / create the objects you desire
The "threading" issue is a big one, as without the "hierarchy" gems, you won't be able to call the "child" objects of the record you want, thus preventing the threading from occurring. Here's a good Railscast on how to achieve it:
The trick with this is to use something called "recursion"
Recursion is where you create an "indefinite" loop, so far as how "recursive" the data is. EG if you have an object with children, you'll have to cycle through the children, and then the children of those children, recursively until you reach the point of showing all the data:
Recursion is the process of repeating items in a self-similar way. For
instance, when the surfaces of two mirrors are exactly parallel with
each other, the nested images that occur are a form of infinite
recursion.
As such, here's how you to it:
Make sure you save your objects with the correct parents
To display the "threaded" conversation, loop through those parents
Use recursion to loop through their children
We use the ancestry gem, which stores the hierarchy slightly differently to the closure_tree gem we've since discovered (intend to use the closure tree gem soon).
You firstly have to therefore save any hierarchy yourself:
This will allow you to save the various "parents" for that object. This means that when you load the object, and wish to cycle through its descendent, you'll be able to use the Ancestry object methods:
Which means you'll be able to use the following:
#app/views/comments/index.html.erb
<%= render partial: "comments", locals: { collection: #comments } %>
#app/comments/_comments.html.erb
<% collection.arrange.each do |comment, sub_item| %>
<%= link_to comment.title, comment_path(comment) %>
<% if category.has_children? %>
<%= render partial: "category", locals: { collection: category.children } %>
<% end %>
<% end %>
To solve the error you have, try to set :inverse_of attribute of has_many and belongs_to statements in your model classes. You can end up having two has_many - one per each belongs_to reverse:
user.rb:
has_many :from_messages, :class_name => 'Message', :foreign_key => "sender_id", :inverse_of => :sender
has_many :to_messages, :class_name => 'Message', :foreign_key => "to_id", :inverse_of => :recipient
message.rb:
belongs_to :sender, :class_name => 'User', :inverse_of => :from_messages
belongs_to :recipient, :class_name => 'User',:inverse_of => :to_messages
Overall I think your approach is a good starting point for a messaging system. You can try to post your code to https://codereview.stackexchange.com/ for a detailed review.
I have an index page that hides/shows different Authors by using a boolean. The problem that I am having is that signed in users can still access hidden authors and their books through the URL.
How can I prevent current users from navigating to hidden Authors and their corresponding Books through the URL? Is there a way to redirect them back to Authors Page if author is hidden?
Currently, I have used the Controllers & a boolean value to help hide/show Authors or Books from signed in users. Can someone please point me in the right direction. Here is my Code.
MODELS
class Author < ActiveRecord::Base
attr_accessible :name, :photo
has_many :books
end
class Book < ActiveRecord::Base
attr_accessible :author_id, :title, :photo
belongs_to :author
end
CONTROLLERS
class AuthorsController < ApplicationController
before_filter :signed_in_user, only: [:index]
before_filter :admin_user, only: [:edit, :update, :destroy, :new, :show]
respond_to :html, :js
###Only displays unhidden authors to non admin users.
def index
if current_user.admin?
#authors = Author.all(:order => "created_at")
else
#authors = Author.where(:display => true).all(:order => "created_at")
end
end
private
def signed_in_user
unless signed_in?
store_location
redirect_to (root_path), notice: "Please sign in."
end
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
class BooksController < ApplicationController
before_filter :signed_in_user, only: [:index]
before_filter :admin_user, only: [:edit, :update, :destroy, :new, :show]
before_filter :get_author
respond_to :html, :js
def get_author
#author = Author.find(params[:author_id])
end
def index
#books = #author.books
end
private
def signed_in_user
unless signed_in?
store_location
redirect_to (root_path), notice: "Please sign in."
end
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
VIEWS
Authors index.html.erb
<% #authors.each do |author| %>
<%= link_to (image_tag author.photo(:medium)),
url_for(author_books_path(author)),
class: "img-rounded" %>
<% end %>
### How Can I prevent Users from accessing Hidden Author's Books (Index Page)
Books index.html.erb
<% #books.each do |book| %>
<%= image_tag book.photo(:medium) %>
<%= book.name %>
<% end %>
ROUTES
resources :authors do
resources :books
end
SCHEMA
create_table "authors", :force => true do |t|
t.string "name"
t.boolean "display", :default => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "photo_file_name"
t.string "photo_content_type"
t.integer "photo_file_size"
t.datetime "photo_updated_at"
end
create_table "books", :force => true do |t|
t.integer "author_id"
t.string "title"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "photo_file_name"
t.string "photo_content_type"
t.integer "photo_file_size"
t.datetime "photo_updated_at"
end
Use a proper authorization model, such as CanCan.
The key part (for CanCan) would be in authorizing based on user roles:
if user.role == admin
can :manage, :all
else
can :read, Author, display: true
end
There is a helpful RailsCast to step you through using CanCan for authorization.
Other options exist, such as Declarative Authorization, or you can roll your own.
Use a proper authorization model, such as CanCan.
Personally, I'm not sure I agree with this, at least given the scope of the issue you seem to be trying to solve. However, your comments in /app/views/books/index.html.erb seem to indicate you wanted to place some logic in your view file. Do NOT do this. Following proper MVC architecture, what you're attempting to do falls under the category of business logic. As such, the code that controls this should be in your controllers.
In your /app/controllers/book_controller.rb file, change the action for an Author's books page to redirect back to the author depending on the author's attributes. Something like this: (not sure what the exact path would be):
def index
# Redirect if author is set to hidden
if !#author.display
redirect_to author_path
else
#books = #author.books
end
end
in the Authors#show Controller, you can write for example --
Authors#Show
def show
#author = Author.find(params[:id])
redirect_to root_url unless #author.display
end
In this case, when a user visits any author's url, it will check to see if that author's display attribute is true or not, and redirect accordingly.