Given 2 resources:
jsonapi_resources :companies
jsonapi_resources :users
User has_many Companies
default_paginator = :paged
/companies request is paginated and that's what I want. But I also want to disable it for relationship request /users/4/companies. How to do this?
The best solution I found will be to override JSONAPI::RequestParser#parse_pagination like this:
class CustomNonePaginator < JSONAPI::Paginator
def initialize
end
def apply(relation, _order_options)
relation
end
def calculate_page_count(record_count)
record_count
end
end
class JSONAPI::RequestParser
def parse_pagination(page)
if disable_pagination?
#paginator = CustomNonePaginator.new
else
original_parse_pagination(page)
end
end
def disable_pagination?
# your logic here
# request params are available through #params or #context variables
# so you get your action, path or any context data
end
def original_parse_pagination(page)
paginator_name = #resource_klass._paginator
#paginator = JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none
rescue JSONAPI::Exceptions::Error => e
#errors.concat(e.errors)
end
end
Related
I am trying to learn how to use Rails 5 (generally) but specifically, I'm trying to learn how to use service classes.
I'm trying to write a service class that maps a user's given email address (user's have an attribute called :email) to organisation's domain names. Organisations have attributes called :email_format. I use that attribute to hold the part of the email address that follows the "#".
When a user creates an account, I want to take their email address that they use to sign up with, and match the bit after the # to each of the organisations that I know about and try to find a matching one.
My attempts at this are plainly wrong, but I'm struggling to figure out why.
I have resources called User, Organisation and OrgRequest. The associations are:
User
belongs_to :organisation, optional: true
has_one :org_request
Organisation
has_many :org_requests
has_many :users
OrgRequest
belongs_to :user
belongs_to :organisation
I have tried to write a service class as:
class User::OrganisationMapperService #< ActiveRecord::Base
def self.call(user: u)
new(user: user).call
end
def initialize(user: u)
self.user = user
end
def call
if matching_organisation.present?
# user.organisation_request.new(organisation_id: matching_organisation.id)
# user.update_attributes!(organisation_id: matching_organisation.id)
else
#SystemMailer.unmatched_organisation(user: user).deliver_now
end
end
private
attr_accessor :user
def matching_organisation
# User::OrganisationMapperService.new(user).matching_organisation
User::OrganisationMapperService.new(user: user)
end
end
I then have an org requests controller with:
class Users::OrgRequestsController < ApplicationController
before_action :authenticate_user!, except: [:new, :create, :requested]
before_action :set_org_request, only: [:approved, :rejected, :removed]
# skip_before_action :redirect_for_unrequested_organisation
# skip_before_action :redirect_for_unknown_organisation
def index
organisation = Organisation.find_by(owner_id: current_user.id)
return redirect_to(user_path(current_user.id)) if organisation.nil?
#org_requests = organisation.org_requests
end
def new
#all_organisations = Organisation.select(:title, :id).map { |org| [org.title, org.id] }
#org_request = OrgRequest.new#form(OrganisationRequest::Create)
matched_organisation = User::OrganisationMapperService.new(current_user).matching_organisation
#org_request.organisation_id = matched_organisation.try(:id)
end
def create
#org_request = OrgRequest.new(org_request_params)
#org_request.user_id = current_user.id
if #org_request.save
OrgRequest::ProcessService.new(org_request).process
return redirect_to(user_path(current_user),
flash[:alert] => 'Your request is being processed.')
else
# Failure scenario below
#all_organisations = Organisation.select(:title, :id).map { |org| [org.title, org.id] }
render :new
end
end
def requested
# Need help - if this is contained in form inputs - how do i stop from overriding the submit path?
redirect_to(user_path(current_user))
#not sure about this - a similar redirect isnt required for articles or project create
end
def approve
#org_request = current_user.organisation.org_requests.find(params[:id])
if #org_request.state_machine.transition_to!(:approved)
flash[:notice] = "You've added this member."
redirect_to org_requests_path
else
flash[:error] = "You're not able to manage this organisation's members"
redirect_to :index
end
end
def remove
#org_request = current_user.organisation.org_requests.find(params[:id])
if #org_request.state_machine.transition_to!(:removed)
flash[:notice] = "Removed from the organisation."
redirect_to action: :index
# format.html { redirect_to :index }
# format.json { render :show, status: :ok, location: #project }
# redirect_to action: :show, id: project_id
# add mailer to send message to article owner that article has been approved
else
flash[:error] = "You're not able to manage this organisation's members"
redirect_to(user_path(current_user))
# redirect_to action: :show, id: project_id
end
end
def decline
#org_request = current_user.organisation.org_requests.find(params[:id])
if #org_request.state_machine.transition_to!(:declined)
flash[:notice] = "You're not eligible to join this organisation"
redirect_to action: :index
# redirect_back(fallback_location: root_path)
# format.html { redirect_to :index }
# redirect_to action: :show, id: organisation_request.profile
# add mailer to send message to article owner that article has been approved
else
flash[:error] = "You're not able to manage this organisation's members"
redirect_to(user_path(current_user))
# redirect_to action: :show, id: organisation_request.profile
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_org_request
#org_request = OrgRequest.find(params[:id])
authorize #org_request
end
# Never trust parameters from the scary internet, only allow the white list through.
def org_request_params
params.require(:org_request).permit(:organisation_id, :name) # Need help - not sure if I need to put user id and organisation id in this permission
end
end
I can't figure out another approach to this. When I try this, I get this error:
wrong number of arguments (given 1, expected 0)
The error message highlights line 7 of my service class, which has:
def initialize(user: u)
self.user = user
end
I have previously asked questions about this problem here: superclass mismatch for class User - inheriting from ActiveRecord::Base
but I haven't managed to catch the drift of the advice or what is causing the problem. This attempt is a mash up of suggestions that I have gleaned from at least 10 different tutorials - so I appreciate that its highly unlikely to be correct, but I'm struggling to understand how the different parts of this work to know what to try differently.
Can anyone give me a steer on how to try to progress this attempt?
Organisation mapper decorator has:
class User < ActiveRecord::Base
class OrganisationMapper < ::ApplicationDecorator
def matching_organisation
#matching_organisation ||= Organisation.by_email_format(email_format).first
end
def email_format
user.email.split('#').last
end
private
def user
#model
end
end
end
Application decorator has:
class ApplicationDecorator
def initialize(model)
#model = model
end
private
def method_missing(method, *args)
args.empty? ? #model.send(method) : #model.send(method, *args)
end
end
Org request service class has:
class OrgRequest::CreateService < ActiveRecord::Base
attr_accessor :org_request
def self.call(user_id: user_id, organisation_id: org_id)
new(user_id: user_id, organisation_id: organisation_id).call
end
def initialize(user_id: user_id, organisation_id: org_id)
self.user_id = user_id
self.organisation_id = organisation_id
end
def call
self.org_request \
= OrgRequest.new(user_id: current_user.id,
organisation_id: params[:org_request][:organisation_id])
if org_request.save
# send the email
true
else
false
end
end
end
NEXT ATTEMPT
I have tried every variation on this that I can think of. Nothing I'm trying makes any sense to me but I can't make sense out of any examples that I can find.
My service class currently has:
class User::OrganisationMapperService #< ActiveRecord::Base
def self.call(user: u)
new(user: user).call
end
def initialize(user: u)
self.user = user
end
def call
# if matching_organisation.present?
# user.org_request.new(organisation_id: matching_organisation.id)
# if found create a request for that user to enter the organisation
if match_domain.present?
OrgRequest.create(user: #user, organisation_id: #organisation_domain.organisation.id) #if organisation
# user.update_attributes!(organisation_id: matching_organisation.id)
else
#SystemMailer.unmatched_organisation(user: user).deliver_now
end
end
private
attr_accessor :user
# def matching_organisation
# # User::OrganisationMapperService.new(user).matching_organisation
# User::OrganisationMapperService.new(user: user).Organisation.by_email_format(email_format).first
# end
# def matching_organisation
# #matching_organisation ||= Organisation.by_email_format(email_format).first
# end
def user_domain
user.email.split('#').last
end
def organisation_domain
#organisation = Organisation.find_by(email_format: user_domain)
end
# def user_email_domain
# # extract domain from users email
# user_email_domain = #user.email.split('#').last
# end
def match_domain
return unless #user_domain == #organisation.email_format
end
# find an organisation with a matching domain
# end
end
It's plainly wrong. The error message says:
NameError - undefined local variable or method `organisation' for #<User::OrganisationMapperService:0x007faec6ec06b8>
I can't make sense of the error message either because I have put '#' in front of every instance of 'organisation' just to try to make that error go away. It doesn't.
Please help.
ANOTHER COMPLETELY SENSELESS ERROR MESSAGE
I had another go at trying to write the method to check whether an email domain matches an organisation's email format in my service class.
The call method now has:
def call
if user_domain == Organisation.email_format.any?
OrgRequest.create(user: #user, organisation_id: #organisation_domain.organisation.id) #if organisation
else
end
end
The error message in the console says:
NoMethodError - undefined method `email_format' for #<Class:0x007faec72d8ac0>
That has to be nonsense because my organisation table has an attribute in it called :email_format. In the console, I can write:
o = Organisation.first.email_format
Organisation Load (3.3ms) SELECT "organisations".* FROM "organisations" ORDER BY "organisations"."id" ASC LIMIT $1 [["LIMIT", 1]]
That gives me the result I'm looking for.
I'm trying (to my wits end) to learn how rails communicates. I can't make any sense of any of it.
NEXT ATTEMPT
Next guess of a go at the call method:
def call
if user_domain == organisation_domain?
OrgRequest.create(user: #user, organisation_id: #organisation_domain.organisation.id) #if organisation
else
end
Produces this error:
NoMethodError - undefined method `organisation_domain?' for #<User::OrganisationMapperService:0x007faec3be3600>:
I can't seem to find a single form of expression that doesnt produce this error.
The problem appears to be in the following line:
matched_organisation = User::OrganisationMapperService.new(current_user).matching_organisation
It should be this instead:
matched_organisation = User::OrganisationMapperService.new(user: current_user).matching_organisation
I had a session on code mentor. This is the answer. I hope it might help someone else who is trying to learn.
class User::OrganisationMapperService #< ActiveRecord::Base
def self.call(user: u)
new(user: user).call
end
def initialize(user: u)
self.user = user
end
def call
if organisation_domain.present?
OrgRequest.create(user: #user, organisation_id: organisation_domain.id) #if organisation
else
end
end
private
attr_accessor :user
def user_domain
user.email.split('#').last
end
def organisation_domain
#organisation ||= Organisation.find_by(email_format: user_domain)
end
end
I'm trying to redirect users to the next instance of my WordExposition model after update. What I have currently works for immediately-adjacent word_exposition id's, but raises RecordNotFound if the next lesson's word_exposition's ID skips (i.e. it will redirect properly between id's 1-4, but will break if the next id is 6). How can I get it to redirect also for those non-adjacent WordExposition instances that belong to the same lesson?
I based the next_exposition model method on the ideas from this post, but I'm missing something to get it to work here.
WordExposition model:
class WordExposition < ActiveRecord::Base
belongs_to :enrollment
belongs_to :word
def next_exposition
WordExposition.where(["id > ? AND enrollment_id = ?", id, enrollment_id]).first
end
end
WordExpositions controller:
class WordExpositionsController < ApplicationController
def update
current_word_exposition
#current_word_exposition.completed = true
#current_word_exposition.term_given_by_student = params[:word_exposition][:term_given_by_student]
if #current_word_exposition.save
flash[:notice] = "Congratulations!"
#currently only redirects correctly for adjacent words in the same lesson, should do so for non-adjacent word_expositions in the same lesson
if next_word = #current_word_exposition.next_exposition
redirect_to lesson_word_exposition_path(current_lesson, next_word)
end
else
flash[:alert] = "Enter the word exactly as shown!"
redirect_to lesson_word_exposition_path(current_lesson, current_word_exposition)
end
end
private
helper_method :current_lesson
def current_lesson
#current_lesson ||= Lesson.find(params[:lesson_id])
end
helper_method :current_enrollment
def current_enrollment
#current_enrollment ||= Enrollment.find_by!(lesson_id: params[:lesson_id], user_id: current_user.id)
end
def word_exposition_params
params.require(:word_exposition).permit(:completed)
end
helper_method :current_word_exposition
def current_word_exposition
#current_word_exposition ||= current_enrollment.word_expositions.find_by!(word_id: params[:id])
end
end
You can try this
def next_exposition
WordExposition.where('id = (select min(id) from word_expositions where id > ?)', self.id).first
end
Each User has_one :family_tree.
So the family_tree route looks like a normal resources :family_trees.
I have a route that looks like this:
get "dashboard/my_tree" => "dashboard#my_tree", as: :my_tree, path: "/my_tree"
What I want to happen is, whenever someone goes to family_tree/:my_id they should be redirected to (or just shown the URL path for) /my_tree. Please note: that the :my_id is the ID of the family_tree that belongs to the current_user.
The issue is that my FamilyTree#Show controller action looks like this:
def show
#user = #family_tree.user
#memberships = #family_tree.memberships
#memberships_grouped_by_relations = #memberships.includes(user: :family_tree).group_by(&:relation)
#nodes = #family_tree.nodes
render "dashboard/my_tree"
end
And my DashboardController#MyTree looks like this:
def my_tree
#user = current_user
#family_tree = #user.family_tree
#memberships_grouped_by_relations = #family_tree.memberships.group_by(&:relation)
end
Both work, but I just don't know how to mask the URL of family_tree/51 to redirect to my_tree. But, obviously, I don't want all requests to family_tree/:id to show /my_tree. E.g. if the family_tree associated with the current_user is id=51, then when that user goes to family_tree/52, that URL should say 'family_tree/52`.
# app/controllers/family_trees_controller.rb
class FamilyTreesController < ApplicationController
# family_tree GET /family_trees/:id(.:format)
#
# #note !IMPORTANT
# Should only have 1 or 2 instance vars per action
def show
# #user = #family_tree.user #=> not needed, available on the primary instance var (#family_tree)
if current_user == family_tree.user
redirect_to my_tree_index_path
else
# #memberships = #family_tree.memberships
# #memberships_grouped_by_relations #=> too long of a name!
# There is currently only one grouped membership,
# why not rename it to:
#grouped_memberships = family_tree.memberships.includes(user: :family_tree).group_by(&:relation)
# Shouldn't create another ivar if it's available on the primary ivar
# #nodes = #family_tree.nodes
end
end
protected
def family_tree
#family_tree ||= FamilyTree.find(params[:id])
end
end
# app/controllers/my_trees_controller.rb
class MyTreesController < ApplicationController
# my_tree_index GET /my_tree(.:format)
def index
#grouped_memberships = current_user.family_tree.memberships.group_by(&:relation)
end
end
And the routes:
# config/routes.rb
My::Application.routes.draw do
resources :family_trees
resources :my_tree, only: :index
end
I want add any kind of permissions for my rails models just including one module to the model and defining metadata in one database field. How i can do this?
For example:
Folder < AR::B
#permissions_list = [:is_private, :public_on_negotioation]
#permissions_field = :perms
include Permissions
end
module Permissions
"...?"
end
i want to have methods "is_private?", "is_private", "is_private=" for all items in a #permissions_list variable.
So i can use model in this way:
f = Folder.new
f.is_private = true
f.public_on_negotioation = false
f.save
f.reload
f.is_private?
=> true
f.public_on_negotioation?
=> false
so i wrote next Module:
module Permissions
def self.included(mod)
permissions_list = mod.instance_variable_get(:#permissions_list)
permissions_list.each_with_index do |permission, index|
define_method permission.to_sym do
perms_bits[index] == '1'
end
alias_method (permission.to_s << "?").to_sym, permission.to_sym
end
end
def perms_bits
send(self.class.instance_variable_get(:#permissions_field)).to_i.to_s(2).reverse
end
def set_permission(name, weight, options)
permissions_field = self.class.instance_variable_get(:#permissions_field)
if options[name]
self.send("#{permissions_field}=", self.send(permissions_field).to_i + weight.to_i) unless send(name)
elsif options.has_key?("#{name}_off")
self.send("#{permissions_field}=", self.send(permissions_field).to_i - weight.to_i) if send(name)
end
end
def update_perms(options)
permissions_list = self.class.instance_variable_get(:#permissions_list)
permissions_list.each_with_index do |permission, index|
set_permission(permission.to_sym, 2**index, options)
end
save
end
end
some improvements?
To extend the answer from mdesantis. The way you can wrap up the permissions code for reuse could be something like this (untested):
class Folder < ActiveRecord::Base
include Permissions
end
PERMISSIONS_STRUCT = Struct.new(:is_private, :public_on_negotiation)
module Permissions
def self.included(klass)
klass.class_eval do
serialize :permissions, PERMISSIONS_STRUCT
end
klass.include(InstanceMethods)
end
module InstanceMethods
def is_private?
permissions.is_private
end
def is_private=(is_private)
permissions.is_private = is_private
end
end
end
Take a look at ActiveRecord::serialize:
Folder < AR::B
# Must be costant, otherwise Rails will raise an
# ActiveRecord::SerializationTypeMismatch
PERMISSIONS_STRUCT = Struct.new(:is_private, :public_on_negotiation)
serialize :permissions, PERMISSIONS_STRUCT
def is_private?
permissions.is_private
end
def is_private=(is_private)
permissions.is_private = is_private
end
# The same for public_on_negotiation
end
f = Folder.new
f.is_private = true
f.save
f.reload
f.is_private?
=> true
If you need to dynamically define accessor methods:
Folder < AR::B
[:is_private, :public_on_negotiation].each do |action|
define_method("#{action}?") do
permissions.send action
end
end
# And so on for "#{action}=", ...
end
And remember: refactoring is up to you! :-)
I've been building messaging in a rails app for users to be able to send each other messages. I've looked at a few gems such as mailboxer but ultimately decided to build my own.
I'm hoping someone can help me put these pieces together. I've been following a similar question's answer here.
I'm testing in the rails console and I keep getting the following error:
undefined method `send_message' for #
How can I fix this?
Controller
class MessagesController < ApplicationController
# create a comment and bind it to an article and a user
def create
#user = User.find(params[:id])
#sender = current_user
#message = Message.send_message(#sender, #user)
flash[:success] = "Message Sent."
flash[:failure] = "There was an error saving your comment (empty comment or comment way to long)"
end
end
Routes
resources :users, :except => [ :create, :new ] do
resources :store
resources :messages, :only => [:create, :destroy]
end
Messages Model
class Message < ActiveRecord::Base
belongs_to :user
scope :sent, where(:sent => true)
scope :received, where(:sent => false)
def send_message(from, recipients)
recipients.each do |recipient|
msg = self.clone
msg.sent = false
msg.user_id = recipient
msg.save
end
self.update_attributes :user_id => from.id, :sent => true
end
end
You are invoking the method on a class level: Message.send_message. For this to work, it would expect a declaration like this:
def self.send_message(from, recipients)
# ...
end
But, you got this instead:
def send_message(from, recipients)
# ...
end
So, either invoke the method on the instance you need it for, or refactor to make it work on a class level.