Subscription based restrictions for rails queries - ruby-on-rails

I have a rather complex issue that I could some assistance with, as all implementations I've been trying are flawed (Rails 4.2.1).
Users have access to devices:
create_table "devices", force: :cascade do |t|
t.string "device_guid"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_ids"
end
and devices have many data entries:
create_table "datas", force: :cascade do |t|
t.string "device_id"
t.datetime "start_time"
t.datetime "end_time"
end
We want to restrict all queries to the data based on Subscriptions.
create_table "subscriptions", force: :cascade do |t|
t.integer "device_id"
t.integer "user_id"
t.datetime "start_date"
t.datetime "end_date"
end
So effectively, data should not be accessed unless Subscriptions allows it.
I wanted to have this restriction done in one place (not by individual method to access it), so other people writing code still deal with the subscription restriction. I don't want to use a 3rd party gem as the data is very sensitive.
I thought the most obvious way to do this would be set a default_scope on the Data model, but I ran into several problems:
1) You can't access #user from the Data model. I got around this with an ugly hack to access User.current from the model.
2) I can't use current_user since we have admins that pose as other users, and need to see the data as they would. Used the same workaround as #1
3) There can be multiple subscriptions with different dates for the same user and device. There isn't any way to do an ActiveRecord Query that I know of that can account for two separate date ranges (I got around this by using arel_table).
4) This makes all of the methods written that query the data to fail when I'm in the rails console (and theoretically through an API interface). I got around this by ignoring the default scope if there is no User.current, but obviously that's a security concern.
This is my current implementation (in psuedocode):
query = []
Subscription.each do |s|
if (User.current.id == s.user_id)
temp = Data.all.where(data[:start_time] >= s.start_date).where(data[:end_time] <= s.end_date).where(data[:device_id] == Device.find_by(id: s.device_id).device_guid)
end
end
query += temp
end
default_scope { query }
I put this code just directly into the model. I didn't think it would work, but it does... sometimes. It acts oddly, sometimes completely restricting a user when it shouldn't, although it hasn't (yet) given somebody access to data they should not have.
I'm looking to redesign this from the ground up, and do it the best way. I'm open to any suggestions (except 3rd-party, sorry) on how best to approach it. I don't mind if it takes a lot of work to implement, I'd rather do it right this time. Any account for expandability (such as if we implemented an API for remote queries) would be a great feature as well, though we don't currently need it.
Just to reiterate, I insist that this restriction be "automatic" to all queries, so that if a front coder for instance writes:
<%= #user.device.find(1).data.all.count %>
in a view, it won't show any data the user doesn't have a subscription for.
Thanks for any suggestions. Sorry for the long wall of text, this has been a rather perplexing problem.

Related

Ruby on Rails 5 strong parameters

I'm having some troubles with a project I'm working on. Be warned I consider myself very much a beginner/novice at all this still :)
To keep things short and sweet, I'm using Rails & active admin to build up an admin interface where i can perform CRUD operations on my database models, which is all working great. However I recently decided I wanted to add another field to one of my models, a "description" field, so generated a migration, ran rake db:migrate and updated my list of allowed params in my controller & active admin resource.
My problem is data is not saved for this new "description" field - wether its via creating a new entry or updating an existing one. I can see the output in the terminal confirms it is being filtered out by strong params; returning Unpermitted parameter: :Description However i am under the impression i have set up my strong params correctly, so I'm unsure if i have set up my permit params properly or what else i can do.
Using Rails 5.1.0 & will post code below.
class CellsController < InheritedResources::Base
def index
end
private
def cell_params
params.require(:cell).permit(:name, :description)
end
end
#database schema for my cell model
create_table "cells", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "Description"
end
#Active Admin resource
ActiveAdmin.register Cell do
permit_params :name, :description
end
Again, greatly appreciate any help as I'm sure I've overlooked something, happy to provide any other information that is required :)
Thankyou!
To me it looks like the description param is not accepted because the model only has a Description column (with a capitalised D). To fix that, either change each params.permit(:description) to params.permit(:Description) or just rename the column inside a new migration:
def change
rename_column :cells, :Description, :description
end
I recommend renaming the column as it will avoid any trouble with the column in the future.

Ruby on Rails: wrong number of arguments (0 for 1) even if provided

We inherited a rails project (the whole thing is based on elasticsearch). Thus, the application lists all the documents meeting the provided search criteria by a user. Imagine a facet or so, once you've selected i.e. a specific range of dates it gives you the documents that were created at that time.
We have been currently working on expanding its functionality accordingly:
Our priority is to utilize the elastic query such that all
matched documents by that query could be multiply altered (in
our case, we've been trying to hide all the documents). Then we want to send the query to appropriate rake task that would take care of it.
After the button responsible for mass hiding is clicked the controller's create method gets called, thereby creating a mass_hiding record in database (for convenience we keep a track of hidings which would allow us to revert the mass action afterwards).
def create
mh = current_user.mass_hidings.build(params[:mass_hiding])
mh.save!
mass_hide(mh.query_params)
redirect_to search_documents_path(mass_hide.search_parameters)
end
def mass_hide(query)
search = factic.create_restrictions_search(MultiJson.load(query.to_json))
Resque.enqueue(Document::Jobs::HideDocuments, nil, search.to_scrollable.build_query)
end
However, the line 3 of the create method mass_hide(mh.query_params) triggers the following exception once it gets executed:
ArgumentError in Admin::MassHidingsController#create
wrong number of arguments (0 for 1)
Full trace can be found here.
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"6mZvRcp4HJuoBWMRToA2gPec9Wv8T82hiTJQ/STf1j/sDhQ+16mBW3QkRmhqlJIHKR0kvX/kqwQh205hp6RuDg==",
"mass_hiding"=>{"serialized_query_params"=>"{}",
"description"=>"test"},
"commit"=>"Hide documents"}
Further, here is the schema representation of mass_hiding as well as the appropriate model:
schema.rb
create_table "mass_hidings", force: :cascade do |t|
t.integer "user_id"
t.text "serialized_query_params"
t.string "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
mass_hiding.rb
class MassHiding < ActiveRecord::Base
attr_accessible :serialized_query_params, :description
belongs_to :user
def query_params=(params)
self.serialized_query_params = Oj.dump(params)
end
def query_params
Oj.load(self.serialized_query_params).with_indifferent_access
end
def self.find_or_initialize_by_user_and_query_params(user, query_params)
self.find_or_initialize_by_user_id_and_serialized_query_params(user.id, Oj.dump(query_params))
end
end
Thank you for your help in advance.
The problem is that you have defined this helper function:
def mass_hide(query)
But you are calling it with zero arguments.
First you call it with one argument, when you say mass_hide(mh.query_params). That's fine.
But then on the next line you also say mass_hide.search_parameters. To Ruby that means mass_hide().search_parameters. Perhaps you meant to write mh.search_parameters? I'm not sure. But mass_hide is the method that wants 1 param, and that's the callsite where you pass it 0.
I hope that helps!

Activity stream in angular or rails?

I have rails/angular app where users can add movies to their watchlist, users can also create friendships with other users. Now I would like to create a activity stream that notifies users when a friend has added a movie to their watchlist. But I can't find any good resources on how to get started on this feature.
For the friendships I have a friendship model,
create_table "friendships", force: :cascade do |t|
t.integer "user_id"
t.integer "friend_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
What would be the connection between users, friends and newly added records (movies)?
You can try public_activity gem. I believe you find it usefull and the documentation provides almost every case you describe.
You have one screencast on the subject here and it supports both Rails 3.x and 4.x.
About your model, I believe you should start first by creating an relational model of your data and then translate it to Rails relationships.
You can try
Paper trail
Any time a change is made in your system, paper trail can track the user id via a whodunnit property. This is set automatically if your controller makes a current_user object available. . it will also track any user, regardless of whether there is an actual relationship between the user and the model.
https://github.com/pokonski/public_activity. It's pretty simple to install and setup.
Also there is Audited
PaperTrail, Vestal versions and Acts as versioned are all very powerful versioning gem. Yes, you can also use it to audit/track users' activities , but it's not very straight forward because they are 'versioning tools', but not for the 'auditing purpose'
Userstamp and audited are for auditing purposing only. but Userstamp is for older Rails versions ( it's a plugin... which is not supported by Rails3(or 3.1, 3.2?) anymore )

how do you integration OmniAuth (Twitter) to an existing user model?

I am using OmniAuth and I'm able to get the callback successfully working.
When I browse to /auth/twitter and log in using Twitter, it leads me back to /auth/twitter/callback with unformatted text that has user ID, profile image URL, screen name, etc.
I haven't done anything with this output yet and that's where I need help.
I already have an existing authentication system and I want to keep everything the way it is. I am looking to grab from Twitter just the screen name, their oauth_token, their oauth_token_secret and their email address (if possible) to create a new account on my site/login to my site. If I want to keep the exact same behavior of my current website and just want to allow people to login/register using Twitter, how can I do that?
Here's my already existing user table
create_table "users", :force => true do |t|
t.string "name"
t.string "email"
t.timestamp "created_at", :null => false
t.timestamp "updated_at", :null => false
t.string "password_digest"
t.string "remember_token"
Can I add the oauth_token and oauth_token_secret columns to this table and make it work that way?
You can. Check Ryans railscasts.com/episodes/241-simple-omniauth. Also be aware that twitter does not return email. You may also need to change user model to allow null for password etc, if you are planning to allow login either with twitter or with traditional user/pass (you may need to work on validation part if the user chose the later)

Separating multiple models in Rails form submission

Specifically, I have a form for creating a User. One of the fields is Group, which is a separate model. During the User#create action I call Group.find_or_create_by_name to check and see if the Group already exists by pulling out params[:user][:group], and create the Group if it doesn't exist.
But, when I create the User, I can't pass params[:user], because params[:user][:group] is not a group, it's a String. This would be a lot easier if I could supply params[:user] and params[:group] to my controller, instead of everything bundled under a single variable, but I don't know how to do that.
Relevant code:
User#create
#group = Group.find_or_create_by_name(params[:user][:group])
#group.save!
#user = #group.users.build(params[:user])
Partial User Schema
create_table "users", :force => true do |t|
t.string "name", :default => "", :null => false
t.string "email", :null => false
t.integer "group_id"
Partial Group Schema
create_table "groups", :force => true do |t|
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
Params Dump from form submission
{"commit"=>"Register",
"authenticity_token"=>"x1KgPdpJop5H2NldsPtk0+mBDtrmpM/oNABOxjpabIU=",
"utf8"=>"✓",
"user"=>{"name"=>"aName",
"group"=>"aGroupName",
"password_confirmation"=>"[FILTERED]",
"password"=>"[FILTERED]",
"email"=>"anEmail"}}
The best explanation I've run across is this question.
I don't really want to start rearranging the params has inside of my controller as that really makes me queasy, and seems like terrible programming. Which really makes me question whether I've got a bigger problem.
If it goes against Rails conventions to do this, what then is the best way to do it, and why should I not?
I don't think the proper way to do this here is to build the user off the group. That'd be appropriate if you were in an action of the GroupsController that, for example, added new users to a group. I think the best approach here would be to do the following:
#group = Group.find_or_create_by_name(params[:user][:group])
#group.save!
#user = User.new(params[:user])
#user.group_id = #group.id
#user.save
Since you're in the new action of the UsersController, it seems more fitting to be creating a new user instead of building it off an association and then adding that user to the group that either already existed or was just created.
Does that make sense?
So, I'm going to spell out everything I've learned, in case other people are having trouble understanding this like I did. Also, if I get anything wrong here, please correct me.
If you're using a form that creates multiple model instances at once (preferably associated ones), you first need to use the helper accepts_nested_attributes_for in your model definition (probably right underneath your declared associations). The reason for this is it creates a setter method that knows how to write that type of associated model. (Note: you can also define this method yourself in your main model). Once you've done that you can nest a fields_for inside of a form_for, and Rails will know how to make the proper assignments.
I initially thought that accepts_nested_attributes_for was referring to nested resources, which is definitely not the case. If you're looking for more information, refer to section 11.8.3 (pp 343-347) of The Rails 3 Way.

Resources