How to ensure single entry in database with rails? - ruby-on-rails

I have created a table for featured_posts, which has_many posts to keep featured posts in the front page of the website. Now I want to ensure that I am creating only one entry at featured_posts. Looking for something like singleton model entry that can be stored in database. What is rails way to ensure that?
Since this is going to be an one entry table, I think my database overhead will be huge, is there any better way to store this type of single entry?

The way I'd do this is to just have an is_featured boolean column in your posts table and in my validator, ensure there's only one with this as true -- for example:
class model < ActiveRecord::Model
validate :validate, on: :create
def validate(record)
errors.add('Maximum of one featured post') unless model.find(:is_featured => true).length == 1
end

You can achieve this by doing the following:
featured_posts_controller.rb
def new
if featured_post.empty?
# create featured_posts
else
redirect_to featured_posts_path
# or you can throw an error
end
end
Another way is to ensure that no one can access the new and create method:
def FeaturedPostsController < ApplicationController
before_action :check_post_presence, only: [:new, :create]
private
def check_post_presence
redirect_to featured_posts_path if featured_post.exists?
end
end
Finally you can create a default featured post on seed and remove the new and create method altogether by adding the data in db/seeds.rb:
featured_post = FeaturedPost.create title: "Default Posts"

Related

Rails Nested Resource not creating from Array

I am trying to create a simple data puller from the yahoo finance gem. I have a nested route setup for Security that has_many Price
The Price controller is as follows:
class PricesController < ApplicationController
before_action :set_security
before_action :set_price, only: [:show, :edit, :update, :destroy]
def new
#price = Price.new
end
private
def set_price
#price = Price.find_by_name(params[:id])
end
def price_params
params.require(:price).permit(:date, :open, :high, :low, :close, :volume, :security_id)
end
def set_security
#security = Security.find_by_ticker(params[:security_id])
end
end
The nested route works fine to manually create the child Price record from a form. I'm trying to create a Price record for each result in the array that gets generated from the following code:
class Datapuller
def self.download_historical
yahoo_client = YahooFinance::Client.new
data = yahoo_client.historical_quotes("FB")
data.each do |i|
#ticker = Security.find_by_ticker(data[0].symbol )
price = #ticker.prices.new()
price.security_id = #ticker
price.date = data[0].date
price.open = data[0].open
price.high = data[0].high
price.low = data[0].low
price.close = data[0].close
end
end
end
I'd like to call this method from a button link but for now I have the following code on the Security View:
<%= Datapuller.download_historical %>
This also give me the benefit to see the data that is getting loaded from the array.
With the above code I can create a child Price to a parent Security using a form and I can make the call from the yahoo finance gem. However, the array is not creating a Price record for each line in the array.
What am I missing in this?
Thanks!
The main reason why you aren't getting anything saved in your array of #ticker.prices is because you're instantiating them via price = #ticker.prices.new() but you're never saving them. You need to call the following after you set all the attributes:
price.save
to get that record
saved via Active Record and
associated with your #ticker.
If you skim through the Active Record basics guide it will help a ton to understand the life cycle of a record (save, validate, delete, etc).
You also will probably want to look at making your views and routes more resourceful, which is the Rails default. If you use a resource :prices in your routes, Rails will use it's convention to help you along the way.
So for instance, you'd have a create method on your controller that would solely be in charge of calling your Datapuller.download_historical - and that way you can make a button that links to that route just by saying <%= link_to 'Create Price', prices_path, method: :create %> that would create your prices on click. There are more ways to further let Rails help you write less code but that's a start. If you want more reading, I think M. Hartl does a great job of explaining RESTful resources in his intro guide.

Which data model is optimal for a simple tracker of user views?

Here's more of an academic question for you guys. Say I want to create a model in a ruby on rails app to track simple views information. I would like to record the user_id, the URI for the page viewed, and keep track of the number of times the user has visited a page.
Model A: One way to do this would be to create a model View with attributes user_id and page (records the uri), and then create a new entry every time a user opens a page.
Model B: A second way to do this would be to add an attribute "page_views" to the model, to track the number of times the user has accessed that page.
Pros and Cons: Model A would have more information recorded and lead to a larger db than Model B. However, Model B would require that a controller search for an existing user-page combination, and either add views to that entry, or create a new one. This leads to a smaller database, but may be worse in scale due to the need to search for existing entries.
So my question to you guys is: which is more important? Are my thoughts wrong? Am I missing something here (other performance considerations overlooked?)
NoSQL approach to tracking user activity:
model app/models/user.rb
class User < ActiveRecord::Base
include UserModules::Tracker
...
end
mixin app/models/user_modules/tracker.rb
module UserModules
module Tracker
def get
key = "user_" + self.id.to_s
arr = Resque.redis.lrange(key, 0, -1)
arr.map{|i| JSON.parse(i)}
end
def put(controller_name, action_name, details="")
key = "user_" + self.id.to_s
created = Time.now.to_formatted_s(:db)}.to_json
# silent exception handle, so you can do not run Redis localy
begin
Resque.redis.rpush key, {
:controller_name => controller_name,
:action_name => action_name,
:details => details,
:created_at => created
rescue
nil
end
end
end
end
controller app/controller/dashboard.rb
class Dashboard < ApplicationController
after_filter :track, :only => :show
# this action will be tracked
def show
end
# this action will show tracking
def logs_show
render :json => current_user.get
end
...
private
def track
details = "any details...."
current_user.put(controller_name, action_name, details)
end
end
You need to have Redis installed, I prefer to use Resque as common way to setup and initialize Redis via Resque.redis, because it will help you to browse your tracking with resque-web
On of the way to setup Redis is in my gist

Processing data before saving to database

I have decimal field in my DB. Users can input values in two formats: with comma or point (11,11 or 11.11).
But MySQL allows to save data only in 'point' format, so i want to process data before saving with regex like this:
sub(/,/,".")
How can i do it in Rails3?
If I understand you correctly, this could be done in the controller or the model. I might use the before_save callback in the model to achieve this in the following way:
class Item < ActiveRecord::Base
before_save :standardise_numbers
...
protected
# Called before this object is saved to the DB
def standardise_numbers
self.number.sub!(",", ".")
end
end
Where number is the attribute you're wanting to convert.
I assume you don't need to convert it back to comma representation to display to the user? If you do, you may want to look into the internationalisation API for Rails, Il8n. It handles this kind of stuff and more, so definitely worth looking into.
Alternative Solution (edit)
Based on your feedback, my above solution doesn't work since the number is already converted and the decimal part lost when it is passed into the model. A similar piece of code could be used in the controller to intercept and convert the number in the params hash itself:
class PostController < ActionController
before_filter :standardise_numbers, :only => [ :create, :update ]
def create
#post = Post.create(params[:post])
end
protected
# Intercepts the params hash
def standardise_numbers
params[:post][:number].sub!(",", ".")
end
end
This simplifies the create and update methods, allowing you to deal with the hash in the same way you normally would.
I played this it and found this:
Suppose what in form field number, user inputs value '12,13'.
Value from form go to PostController to 'create' method
class PostController < ApplicationController
def create
#post = Post.new(params[:post])
#on this step instance of Post model created, validated and filled with relevant values
#so #post.number == '12' #(decimal), it cuts ',13'
#we need to redefine #post.number
#post.number = params[:post][:number].gsub(/,/,'.').to_f # => 12.13
#and after that save the post
#post.save
end

Rails 3, help controlling access to a record based on the user id

I'm a Rails newbie.... Here's what I'm trying to do....
I created a scaffold for notes (t.text :content, t.integer :user_id)
What I want to do now is only allow user's to view notes that they created. ie (== user_id)
In my /app/controllers/notes_controller.rb
I have the following:
class NotesController < ApplicationController
before_filter :authenticate
before_filter :correct_user
.
.
.
.
def correct_user
#noteuserid = Note.find(:conditions=>["note.user_id=?", #noteuserid])
redirect_to(root_path) unless current_user?(#noteuserid)
end
I'm having problems understanding how to write the following line: #noteuserid = Note.find(:conditions=>["note.user_id=?", #noteuserid])
Any ideas?
Thanks
In Rails 3:
Note.where(:user_id=>current_user)
Or, you can start with the user...
User.find(current_user_id).notes.find(note_id)
So, firstly you want to find the Note being accessed by the user, then check whether that Note is valid for the user. I would try something like this (assuming that your current_user? method checks whether a given user id matches the current logged in user:
def correct_user
current_note = Note.find(params[:id])
redirect_to(root_path) unless current_user?(current_note.user_id)
end
Also, you may want to watch out for filtering all actions in the controller with your correct_user filter as actions to create a note may not have an id of a note to check against. Additionally, when you are viewing a collection of notes you will need to filter differently (e.g. Note.find(:all, :conditions => { :user_id => current_user_id })). It may be more appropriate to apply the correct logic in specific actions rather than as a generic filter.
Finally, you could look at the cancan plugin which would do a lot of the hard work for you with code like this.

Rails plugin for Group of users

My Rails application have a User model and a Group model, where User belongs to a Group. Thanks to this, a user can be a admin, a manager, a subscriber, etc.
Until recently, when for example a new admin need to be create on the app, the process is just to create a new normal account, and then an admin sets the new normal account's group_id attribute as the group id of the admin... using some condition in my User controller. But it's not very clean, I think. Because for security, I need to add this kind of code in (for example) User#update:
class UsersController < ApplicationController
# ...
def update
#user = User.find(params[:id])
# I need to add some lines here, just as on the bottom of the post.
# I think it's ugly... in my controller. But I can not put this
# control in the model, because of current_user is not accessible
# into User model, I think.
if #user.update_attributes(params[:user])
flash[:notice] = "yea"
redirect_to root_path
else
render :action => 'edit'
end
end
# ...
end
Is there a clean way to do it, with a Rails plugin? Or without...
By more clean, I think it could be better if those lines from User#update:
if current_user.try(:group).try(:level).to_i > #user.try(:group).try(:level).to_i
if Group.exists?(params[:user][:group_id].to_i)
if Group.find(params[:user][:group_id].to_i).level < current_user.group.level
#user.group.id = params[:user][:group_id]
end
end
end
...was removed from the controller and the application was able to set the group only if a the current user's group's level is better then the edited user. But maybe I'm wrong, maybe my code is yet perfect :)
Note: in my User model, there is this code:
class User < ActiveRecord::Base
belongs_to :group
attr_readonly :group_id
before_create :first_user
private
def first_user
self.group_id = Group.all.max {|a,b| a.level <=> b.level }.id unless User.exists?
end
end
Do you think it's a good way? Or do you process differently?
Thank you.
i prefer the controller methods to be lean and small, and to put actual model logic inside your model (where it belongs).
In your controller i would write something along the lines of
def update
#user = User.find(params[:id]
if #user.can_be_updated_by? current_user
#user.set_group params[:user][:group_id], current_user.group.level
end
# remove group_id from hash
params[:user].remove_key(:group_id)
if #user.update_attributes(params[:user])
... as before
end
and in your model you would have
def can_be_updated_by? (other_user)
other_user.try(:group).try(:level).to_i > self.try(:group).try(:level).to_i
end
def set_group(group_id, allowed_level)
group = Group.find(group_id.to_i)
self.group = group if group.present? && group.level < allowed_level
end
Does that help?
Well if you have a User/Groups (or User/Roles) model there is no other way to go than that you have underlined.
If it is a one-to-many association you can choose to store the user group as a string and if it is a many-to-many association you can go for a bitmask but nonetheless either through business logic or admin choice you need to set the User/Group relation.
You can have several choices on how to set this relationship in a view.
To expand your model's capability I advice you to use CanCan, a very good authorization gem which makes it super easy to allow fine grain access to each resource in your rails app.

Resources