Rails subscription limitations - ruby-on-rails

I have an application with set limits on subscription attributes i/e a user can have five projects for subscription A but have ten for subscription B
At present I run a check on there current usage when linking to the create action and if they are over there limit I do not display the link to create a new project. On the view (for the new project ) I again run the check (in a helper) to see if they can create a new project and if they are not I display a message stating so and a little upgrade link.
Is this a secure method of stopping a user bypassing there subscription attribute limitations ?
What about direct PUT requests etc ?

You can also validate that the user's subscription allows starting a new project when a new project is created. This guarantee that even if they posted directly to the new_project_path they would get an error.
class Project
belongs_to :user
validate_on_create :subscription_allows_new_project
def subscription_allows_new_project
unless self.user.subscription.max_projects > self.user.projects.count
errors.add_to_base("Project limit reached, please upgrade today!")
end
end
end

If you're really cautious about the put requests, you could simply create a helper method that you call in all of the pages.
<% if has_user_hit_project_limits %>
Upgrade Now!
<% else %>
Add project
<% end %>
def has_user_hit_project_limits
if #logic
true
else
false
end
end

Related

Updating Associated Objects in Rails with Method

I'm trying to update all market data from an API call.
I have a Platform that contains many Markets. The markets have the high, low, latest price, etc.
I can't iterate through the associated collection and call a method to update. Maybe I have the whole structure incorrect, I'm not sure.
I thought it made sense to use the market.update method to refresh the data with an API call.
class MarketsController < ApplicationController
def update
#platform = Platform.find(params[:platform_id])
#market = #platform.markets.find(params[:id])
#market.api_get_market_summary
#market.save
redirect_to #market.platform
end
Which works fine in the Platform view
<% #platform.markets.each do |market| %>
<%= market.market_name %>
<%= market.high %>
<%= market.low %>
<%= link_to 'Update', [market.platform, market],
:method => :put %>
<% end %>
I've tried every combination in the platform controller but I have no idea how I should be do this to update all the markets in the platform.
class PlatformsController < ApplicationController
def update
#platform = Platform.find(params[:id])
#platform.markets.each do |market|
market.update(:id => market.id) # this obviously doesn't work
end
redirect_to #platform
end
Should I be updating all the attributes here with the update_attributes function?
I call the market API update when the object is created so the data gets initialized there which is great.
How should I go about this?
Another part, if I added another platform, how would I handle the different API requests this one would use?
Having the following relationship Platform --- has_many --- Market, if you want to perform an action on the collection of markets, have you considered adding a callback on the Platform model?
class Platform < ApplicationRecord
has_many :markets
after_save :update_markets
...
private
def update_markets
markets.each do |market|
...
end
end
end
Notice that:
after_save runs both on create and update, but always after the more specific callbacks after_create and after_update, no matter the order in which the macro calls were executed.
I'm not sure what are you trying to do here market.update(:id => market.id) but if you're updating only one record on all the markets consider update_all here's a good source
Another part, if I added another platform, how would I handle the different API requests this one would use?
By adding another platform a new Platform object is created and stored with a different ID, when the request hits your controller:
#market = #platform.markets.find(params[:id])
Also, consider #market = Market.find(params[:id]) instead of the above.
You can add this to your Platform model
accepts_nested_attributes_for :markets

How to check if a model instance was updated

I've searched through the site on how to accomplish this and many answers point towards using the changed? method. I'm trying to notify users of an update after persisting the DB so unfortunately this won't work.
I then found the previous_changes method but this also triggers when a post is first created. The goal for is to do this only edit actions. How can this be done?
<% if #post.previous_changes %>
<span>Updated:</span> <span><%= time_ago_in_words(#post.updated_at) %> ago</span>
<% end %>
One possible solution is to set a flag whenever the element is updated.
class Model
after_update :flag_update
def updated?
!!#updated
end
private
def flag_update
#updated = true
end
end
Then in your code simply check #post.updated?.
You have an updated record when
#post.previous_changes.present? && #post.previous_changes["id"].nil?
Sounds like a good candidate for using after_update callback and in it calling a job to send an e-mail, text message or whatever your means of notifying a user is.
Using the callback to flag the model instance basically adds a state for the model and only works once because it is set after the first update. Any subsequent updates might go unnoticed depending on what and how you want to notify your users, unless you could somehow reset the state (flag), which may be a lot more work than what it is worth.
Relying on the updated_at timestamp to be greater than the created_at suffers from the same one-time opportunity than flagging.
We used an approach where we checked what a model's changes returns (you could also use changed?) before saving it, and then sent a notification after a successful save.
If you're able to call previous_changes, why not just add a conditional to make sure that id was not changed (as per this answer):
<% if #post.previous_changes && !#post.id_changed? %>
...
<% end %>

Shopify Rails App: Triggering create callback for a shop re-install

I have a Shopify app that runs a callback when a new Shop is created. I just discovered a bug that when the app is uninstalled, then re-insalled, the callback isn't run because the shop isn't actually created again (I don't delete shops from my DB on uninstall).
class Shop < ActiveRecord::Base
include ShopifyApp::Shop
after_create :init_webhooks
def self.store(session)
shop = Shop.where(:shopify_domain => session.url).first_or_create({ shopify_domain: session.url,
:shopify_token => session.token,
:installed => true})
shop.id
end
def self.retrieve(id)
shop = Shop.where(:id => id).first
if shop
ShopifyAPI::Session.new(shop.shopify_domain, shop.shopify_token)
else
nil
end
end
I could run a check to see if shop.installed = false, and then if it's false, I can init_webhooks. But I'm just not sure where I should be putting this logic. I don't know if it's appropriate to put inside the store or retrieve methods.
I'm wondering if there's something simple that I'm missing. Basically I want to run my init_webhooks if the webhooks don't exist.
EDIT: I tried the below solution of refactoring my callbacks into their own method whereby I could check to see if app is installed and then, if not, run the methods I want on new installs:
def self.retrieve(id)
shop = Shop.where(:id = id).first
if shop
shop.boot
ShopifyAPI::Session.new(shop.shopify_domain, shop.shopify_token)
else
nil
end
end
def boot
if !installed
shopify_session
init_webhooks
self.installed = true
self.save!
end
end
This seems to be working fine for brand new installs, but on a re-install, the user doesn't seem to authenticate (keeps redirecting to the /login page after entering shopify url)<
You can put this check inside an Initializer so when the app is started, it only checks once and does any necessary setup before the rest of the app loads or begins to take requests.
References
http://guides.rubyonrails.org/configuring.html#using-initializer-files

I'm attempting to determine my user's role from within a view helper and am concerned that I'm doing it wrong

I have helpers that are used to determine whether a given user is an administrator or a vendor. From within the helpers, I query the database for their respective role objects (administrator, vendor, etc.) for comparison against roles associated with the user but have a feeling that this is an ass-backward way to go about determine a user's role.
Am I doing something wrong here? What could I do better? I should probably mention that I'm using/learning Pundit, so maybe it contains a better means by which to accomplish this.
Here's my code:
users_helper.rb
1 module UsersHelper
2 def admin?
3 # Determine whether the user has administrator status within a given event
4 #admin_role = Role.find(1)
5 return true if #user.roles.include? #admin_role
6 end
7
8 def vendor?
9 # Determine whether the user is an approved vendor within a given event
10 #vendor_role = Role.find(2)
11 return true if #user.roles.include? #vendor_role
12 end
13 end
Here's how I use the helpers from within my template:
show.html.erb
1 <% provide(:title, #user.username) %>
2
3 <% if admin? %>
4 <p>Admin</p>
5 <% elsif vendor? %>
6 <p>Vendor</p>
7 <% else %>
8 Something else.
9 <% end %>
The associations of User model with roles imply that your User model may have more then one role such as admin or vendor at the same time.
I would suggest to refactor User model to use has_one association with role, so that users will have only one role.
Then the helper in user_helper.rb might be something like:
def is_a?(user)
roles = { Roles.find(1) => "Admin", Roles.find(2) => "Vendor"}
roles.fetch(user.role) do
"Something else."
end
end
This will return string with correct string definition of role. You can make comparison with that or use it in view as follows:
<p><%= is_a?(#role) %></p>
Which does same thing as your code above in one line.
This might be better over at Code Review.
1) Your role implementation is a bit clunky (what with the magic numbers) - do you do anything with the Role class that warrants its being an ActiveRecord class?
If not, you could use the role_model gem (as mentioned by Sontya), which saves the user roles as a bitmask in the users table.
In case you need a more sophisticated role class, you may want to have a look at rolify, which is similar in concept to your current solution.
Either way, user roles in Rails applications are a solved problem, you do not need to write your own solution (of course, don't let that discourage you, building your own solution is at the very least a good learning exercise).
2) Checking for roles in a view is a pretty bad idea, especially if you're already using pundit.
If you ever add a new role, you need to go through all the views and change the if conditions to support the new new role.
This is a problem that explodes when both your number of views and your number of roles grow.
Instead, use the pundit policies in your view, and check for the users role(s) in the corresponding policy object.
Example:
class CompanyPolicy < ApplicationPolicy
def delete?
user.is?(:admin)
end
end
in the view:
<% if policy(#company).delete? %>
delete link here
<% end %>
Don't forget to authorize in the controller as well!
That way, if you add a new role, you simply have to change a few policy objects and you're good to go.
First off all, include? method returns boolean, so:
return true if #user.roles.include? #admin_role
is equal to:
#user.roles.include? #admin_role
Unless you need nil instead of false.
Second, your method names and the code itself should be so simple and self-descriptive, to not require comments. Unless you need automated documentation, when writing public gem or something.
Regarding you question, IMHO you should do this in your User model:
def admin?
roles.where(id: 1).any?
end
Then you can do #user.admin? in the view.

Rails and Authlogic. Show currently logged in users

I would like to have the list of currently logged in users.
This code doesn't work :
<% UserSession.all.each do |user_session| %>
<% end %>
#syed-aslam has a good solution, but you could just let Authlogic do the work. Check out the module Authlogic::ActsAsAuthentic::LoggedInStatus which defines two scopes: logged_in, logged_out
Your code becomes:
<% User.logged_in.each do |user| %>
<% end %>
P.S. I would normally link to the RDoc instead of source code, but the RDoc seems to have problems at the moment.
Authlogic gives you all kind of automatic columns that you don’t really need to update or maintain on your own, they are maintained by the actual code flow of Authlogic itself.
Those fields can contain some basic functionality related issues like the number of login attempts made, the ip address from which the attempt was made an or even what was the ip address the last time that user logged in. fun.
The magic column that will help us find who is probably online is the one called last_request_on, which basically indicates when was the last time that user made a request to your application.
The second parameter we’ll need in order to make a more accurate selection, is the configuration option named logged_in_timeout, which sets the timeout after which a stale session will be expired, by default it will expire after 10 minutes.
so if you set your session expiry to 30 minutes:
class User << ActiveRecord::Base
acts_as_authentic do |c|
c.logged_in_timeout 30.minutes
end
end
searching for those users is pretty easy:
module OnlineUsers
def count_online_users
User.count(:conditions => ["last_request_at > ?", 30.minutes.ago])
end
end
Why not creating a field called currently_active in the user model and update it to true once a session is created, and update to false once the session is destroy.
You can then call User.where(currently_active: true) gives you the users that online.
You cannot get UserSession for all user, UserSession is created every time user sends request and is not remembered between requests.
However you can show users which logged in some period of time (if you have last_logged_in column updated on every signin)
Logged in last 15 minutes:
<% User.find("last_logged_in < ?", 15.minutes.ago ).each do |user| %>
I wrote after_create and before_destroy callbacks in UserSession model.
In after_create callback, I wrote the user id of the user logging in to a text file (self.user.id) and in before_create callback I deleted the same. To check the activity of the user, I read the text file and checked the presence of the user id in that file.

Resources