I have a simple Rails application (not even database driven). And basically I want to make it support two languages. My approach to this is to have two little flags in the footer, one of GB (for English) and one of Germany (for German). When the User clicks on one, the language of the site changes to the corresponding language of the country flag.
I have my [ en.yml ] and [ de.yml ] all setup, and I have used their keys in all my views, and I have even tested that they work by swapping the [ config.i18n.default_locale ] property between [ en ] and [ de ] in [ config/application.rb ]. All works well in that regard.
I am stuck however on how I can achieve the language changing mechanism. I have created a controller called [ Language ] and in it I have defined a method called [ Set ] but other than that, it is pretty much blank, here is what I think should happen:
class LanguageController < ApplicationController
def set
I18n.locale = params[:lang]
redirect_to(:controller => 'home', :action => 'index')
end
end
The above doesn't seem to work. When I call the [ /language/set?lang="de" ] the localized strings change to the keys not the actual German translated text.
This:
I18n.locale = params[:lang]
does not persist across requests, so when you redirect (= a new request) it reverts back to English.
You should save the language setting in a session:
class LanguageController < ApplicationController
def set
session[:lang] = params[:lang].to_sym
redirect_to(:controller => 'home', :action => 'index')
end
end
And check the session on each request. You can do this in a before_filter in your ApplicationController:
class ApplicationController
before_filter :set_language
def set_language
I18n.locale = session[:lang] if session[:lang]
end
end
Related
FriendlyID is consistently showing duplicate content for both /slug and /1. In other words, the correct page is loading for the friendly slug (/new-york), but it's loading the same content for the old, unfriendly slug (/11).
Here's my current configuration:
#config/routes.rb
resources :groups, path: ''
get 'groups/:id' => redirect("/%{id}")
#app/models/group.rb
class Group < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: [:slugged, :finders]
end
#app/controllers/groups_controller.rb
def show
#group = Group.friendly.find(params[:id])
end
As a potential workaround, I've found putting this in my controller does redirect the bad slugs (/11) to the good slugs (/new-york), but it feels wrong for many reasons (routing outside routes.rb, likely unintended consequences, complex solution for a common problem = probably not the right one).
if request.path != group_path(#group)
return redirect_to #group, :status => :moved_permanently
end
What is the right way to make FriendlyID either (1) redirect :id calls to :slug or (2) simply 404 them?
Thanks to this fantastic comment on Medium, I now have a fully functional and very elegant solution which solves my initial problem (duplicate pages with /new-york and /11) as well as allowing two root-level slug structures to coexist.
get '/:id', to: 'groups#show', constraints: proc {|req| FriendlyId::Slug.where(sluggable_type: 'Group').pluck(:slug).include?(req.params[:id])}, as: :group
get '/:id', to: 'custom_pages#show', constraints: proc {|req| FriendlyId::Slug.where(sluggable_type: 'CustomPage').pluck(:slug).include?(req.params[:id])}, as: :custom_page
Super beginner here.
Here's what I am trying to do:
Build a basic to do list app, where User X logs in with facebook, adds some items, sees them, logs out. User Y/Z/M/etc, should be able to log in see their OWN list, add their OWN items, etc.
AKA: a standard web app where you log in to your account and see your own info.
What I have so far:
Ability to build a list, log in with Facebook and having it know your name.
However, the list stays the same whether I log in or whether my friend logs in with her account.
What I need to do, and don't know how:
I need each user to be able to create and see their own list, and be able to come back to it and still see it/ add to it, etc.
I don't even know how this is called, would this be a database of users each with their own set of data? Would the lists need to be set up so they could be stored as a chunk of data?
Does it have something to do with this :Sessions in Sinatra using Facebook authentication If so, what?
If anyone could be give me some really really basic directions as to where to go from here, any tutorials or what I should be googling for, that'd be awesome.
Here's my main piece of code (warning: it's really messy) :
require 'sinatra'
require 'data_mapper'
require 'time'
require 'rubygems'
require 'json'
require 'omniauth'
require 'omniauth-facebook'
#TODO require 'omniauth-att'
SCOPE = 'email,read_stream'
DataMapper::setup(:default, "sqlite3://#{Dir.pwd}/queue.db")
class SinatraApp < Sinatra::Base
configure do
set :sessions, true
set :inline_templates, true
set :protection, :except => :frame_options
end
class Note
include DataMapper::Resource
property :id, Serial
property :content, Text, :required => true
property :complete, Boolean, :required => true, :default => false
property :created_at, DateTime
property :updated_at, DateTime
end
class User
include DataMapper::Resource
property :id, Serial
property :uid, String
property :name, String
property :created_at, DateTime
end
###### no clue what this does ##############
DataMapper.finalize
DataMapper.auto_upgrade!
enable :session
use OmniAuth::Builder do
provider :facebook, '464630283595639','5e4c7ad43bf111c10287c981d51127a3',:scope => SCOPE, :display => "popup"
#provider :att, 'client_id', 'client_secret', :callback_url => (ENV['BASE_DOMAIN']
end
###### root ##############
get '/' do
if current_user
#notes = Note.all :order => :id.desc
#title = 'Movie Queue'
erb :home
else
' sign in with Facebook'
end
end
###### authentication ##############
["/sign_in/?", "/signup/?"].each do |path|
get path do
redirect '/auth/facebook'
end
end
get '/auth/:name/callback' do
auth = request.env["omniauth.auth"]
user = User.first_or_create({ :uid => auth["uid"]}, {
:uid => auth["uid"],
:name => auth["first_name"],
:created_at => Time.now })
session[:user_id] = user.id
redirect '/'
end
helpers do
def current_user
#current_user ||= User.get(session[:user_id]) if session[:user_id]
end
end
##list making part###
post '/' do
n = Note.new
n.content = params[:content]
n.save
redirect '/'
end
get '/:id/delete' do
n = Note.get params[:id]
if n.destroy
redirect '/', :notice => 'Note deleted successfully.'
else
redirect '/', :error => 'Error deleting note.'
end
end
get '/:id/complete' do
n = Note.get params[:id]
n.complete = n.complete ? 0 : 1 # flip it
n.save
redirect '/'
end
########## logout and error handlers #############
get '/logout' do
session[:user_id] = nil
redirect '/'
end
get '/auth/failure' do
erb "<h1>Authentication Failed:</h1><h3>message:<h3> <pre>#{params}</pre>"
end
get '/auth/:provider/deauthorized' do
erb "#{params[:provider]} has deauthorized this app."
end
get '/protected' do
throw(:halt, [401, "Not authorized\n"]) unless session[:authenticated]
erb "<pre>#{request.env['omniauth.auth'].to_json}</pre><hr>
<a href='/logout'>Logout</a>"
end
end
########## don't know what this is #############
SinatraApp.run! if __FILE__ == $0
Disclaimer: I don't know Datamapper, but this should get you going.
There needs to be a way to associate a note with a user. This needs a table in the database, some would call it users_notes, personally I prefer users_rel_notes, (perhaps Datamapper has a convention for this… YMMV). Anyway, the table will have a minimum of 2 columns - the user's id, and the note id. You don't need a separate table as I wrote before (I'm lacking a bit of sleep, sorry!), that would be for a many to many relationship where a user could have several notes and a note could be associated with several users. For what you have, where only the owner of a note has access to it, it requires a one to many relationship. You could add a column to the notes table to store the user id.
Then, in the User class, add an association to the Note class, it's a one to many association and in Datamapper that's a has n, e.g.
has n, :notes
Now when you have a user instance, you can (probably) call the notes for that user via:
user.notes
I see you have the helper current_user defined, so if someone is logged on you could call current_user.notes to get back all the notes for the logged in user.
Remember, when you add a note to make sure you add a record to the association table, (probably, read the link) via user.notes << my_new_note.
The session is the information you keep around to identify the user and any other little bits of info that you may recurrently need. The likelyhood is, you're just storing an id for the user, or the facebook token that identifies them, and then during a request, if it's needed then you'll look inside the cookie, grab the id, look up that user by the id and get a user instance. Session info can be stored in several ways, most often cookies but you can use anything you would use to store any other data.
I'm trying to test a controller to ensure that only an authorized party can view the correct child object using RSpec. I cant figure out what I'm doing wrong as I'm getting this error:
ActiveRecord::RecordInvalid: Validation failed: Company can't be blank
I have a Plan object and a Company object. The Store can have many plans (think of a pest control Company). I want to test that given a known scenario I can retrieve the plan fo the Company (assuming there is only one).
The Plan looks like this:
class Plan < ActiveRecord::Base
before_save :default_values
# Validation
validates :amount, :presence => true
validates :company, :presence => true
# Plans belong to a particular company.
belongs_to :company, :autosave => true
scope :find_all_plans_for_company, lambda {
|company| where(:company_id => company.id)
}
# Other code ...
end
The Company looks like this:
class Company < ActiveRecord::Base
validates :name, :presence => true
validates :phone1, :presence => true
validates_format_of :phone1, :phone2,
:with => /^[\(\)0-9\- \+\.]{10,20}$/,
:message => "Invalid phone number, must be 10 digits. e.g. - 415-555-1212",
:allow_blank => true,
:allow_nil => true
has_many :users
has_many :plans
end
.. controller looks like this
def index
#plans = Plan.find_all_plans_for_company(current_user.company)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #plans }
end
end
.. and my RSpec test looks like this (excuse me if its full of gimmickery, I'm just splunking around with it and cannot get it to work).
describe PlansController do
def valid_attributes
{
:company_id => 1,
:amount => 1000
}
end
describe "GET index" do
it "should return the Plans for which this users company has" do
#company = mock_model(Company, :id => 1, :name => "Test Company", :phone1 => "555-121-1212")
Company.stub(:find).with(#company.id).and_return(#company)
controller.stub_chain(:current_user, :company).and_return(#company)
plan = Plan.create! valid_attributes
get :index, {}
assigns(:plans).should eq([plan])
end
# Other tests ...
end
end
The problem is, when I try this (or any of the crazy other variants I've tried) I get this error:
ActiveRecord::RecordInvalid: Validation failed: Company can't be blank
I'm not sure why this is happening as I thought the Company.stub call would handle this for me. But apparently not.
What am I missing here and what am I doing wrong? How can I get this test to pass?
Let's peel back the layers on this spec, to make sure things make sense (and to make sure I understand what's going on). First, what are you testing?
it "should return the Plans for which this users company has" do
...
assigns(:plans).should eq([plan])
So you want to check that the plans associated with the company of the current user are assigned to #plans. We can stub or mock out everything else.
Looking at the controller code, we have:
def index
#plans = Plan.find_all_plans_for_company(current_user.company)
What do we need to get this to work, without hitting the database and without depending on the models?
First of all, we want to get a mock company out of current_user.company. This is what these two lines in your spec code do:
#company = mock_model(Company, :id => 1, :name => "Test Company", :phone1 => "555-121-1212")
controller.stub_chain(:current_user, :company).and_return(#company)
This will cause current_user.company to return the mock model #company. So far so good.
Now to the class method find_all_plans_for_company. This is where I'm a bit confused. In your spec, you stub the find method on Company to return #company for id = 1.
But really, wouldn't it suffice just to do something like this in your controller code?:
#plans = current_user.company.plans
If you did it this way, then in your test you could just mock a plan, and then return it as the plans association for your mock company:
#plan = mock_model(Plan)
#company = mock_model(Company, :plans => [ #plan ])
controller.stub_chain(:current_user, :company).and_return(#company)
Then the assignment should work, and you don't need to actually create any model or hit the database. You don't even need to give your mock company an id or any other attributes, which anyway are irrelevant to the spec.
Maybe I'm missing something here, if so please let me know.
Why do you need to mock?
My standard testing setup is to use Database Cleaner which clears out the database from any records created during tests. In this way, the tests are run with real database records which are consequently deleted from the test database after each test.
You might also like taking a look at Factory Girl for creating instances of your models during testing (makes it easy to create 10 company records, for example).
See:
http://rubygems.org/gems/database_cleaner
http://rubygems.org/gems/factory_girl
I have three thoughts coming up that could resolve your issue:
Try adding attr_accessible :company_id to Plan class.
Because mock_model does not actually save to the database when you create a Plan with company_id of 1 it fails validation since it is not present in the database.
Ensure before_save :default_values in Plan class does not mess with company_id attribute of the newly created instance.
I'm working on a Rails 3.2 application with the following routing conditions:
scope "(:locale)", locale: /de|en/ do
resources :categories, only: [:index, :show]
get "newest/index", as: :newest
end
I've a controller with the following:
class LocaleController < ApplicationController
def set
session[:locale_override] = params[:locale]
redirect_to params[:return_to]
end
end
I'm using this with something like this in the templates:
= link_to set_locale_path(locale: :de, return_to: current_path(locale: :de)) do
= image_tag 'de.png', style: 'vertical-align: middle'
= t('.languages.german')
I'm wondering why there doesn't exist a helper in Rails such as current_path, something which is able to infer what route we are currently using, and re-route to it include new options.
The problem I have is using something like redirect_to :back, one pushes the user back to /en/........ (or /de/...) which makes for a crappy experience.
Until now I was storing the locale in the session, but this won't work for Google, and other indexing services.
I'm sure if I invested enough time I could some up with something that was smart enough to detect which route matched, and swap out the locale part, but I feel like this would be a hack.
I'm open to all thoughts, but this SO question suggests just using sub(); unfortunately with such short and frequently occurring strings as locale short codes, probably isn't too wise.
If you are using the :locale scope, you can use url_for as current_path:
# Given your page is /en/category/newest with :locale set to 'en' by scope
url_for(:locale => :en) # => /en/category/newest
url_for(:locale => :de) # => /de/kategorie/neueste
In case somebody looks here, you can use request.fullpath which should give you all after domain name and therefore, will include locale.
When using accepts_nested_attributes_for, I got stuck when having a validation which required the original to be present. The code will help clear up that sentence.
class Foo < ActiveRecord::Base
has_one :bar
accepts_nested_attributes :bar
end
class Bar < ActiveRecord::Base
#property name: string
belongs_to :foo
validates_presence_of :foo #trouble line!
end
#now when you do
foo = Foo.create! :bar_attributes => {:name => 'steve'}
#you get an error because the bar validation failed
I would like to write a validation that goes something like...
class Bar < ActiveRecord::Base
validates_presence_of :foo, :unless => :being_built_by_foo?
end
I am currently using rails3.beta4
Thank you
Alas I don't have an answer to this post, but the I came up with another way so I didn't need the validation.
Since bar should never be without a foo then any request to create a bar without a foo_id is an error. In the real example a foo is a project, and bar is a bid. It is a nested resource, but I wanted to give access to json apps to be able to query the info from the /bids location so the router looked like.
resources :bids
resources :projects do
resources: bids
end
and then I just had to make sure all html access used project_bids_path or form_for [:project,#bid] etc. This next part is largely untested but so far the desired behavior is there. I got the idea from Yehuda's post on generic actions http://yehudakatz.com/2009/12/20/generic-actions-in-rails-3/
#I'm sure there is a better way then map.connect
map.connect "projects/invalid_id", :controller => "projects", :action => "invalid_id"
resources :projects
resources :bids
end
#couple of changes from Yehuda
def redirect(*args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
path = args.shift || block
path_proc = path.is_a?(Proc) ? path : proc {|params| path % params }
status = options[:status] || 301
lambda do |env|
req = Rack::Request.new(env)
#Get both the query paramaters and url paramaters
params = env["action_dispatch.request.path_parameters"].merge req.params
url = path_proc.call(params.stringify_keys)
#Doesn't add the port back in!
#url = req.scheme + '://' + req.host + params
#content-type might be a bad idea, need to look into what happens for different requests
[status, {'Location' => url, 'Content-Type' => env['HTTP_ACCEPT'].split(',').first}, ['Moved Permanently']]
end
end
def bid_path
redirect do |params|
if params['project_id']
"/projects/#{params['project_id']}/bids/#{params['id']}"
else
'/projects/invalid_id'
end
end
end
match "bids", :to => bid_path
match "bids/:id", :to => bid_path
however, after doing all of this I most definitely don't think it worth it. I think nested_attributes breaks things and can be improved if that validation doesn't work, but after looking through the code for a little while I'm not sure exactly how to fix it or if it's worth it.
first of all, when using nested_attributes, you'll get the presence of the container. in the example: when you save Foo and there's also a nested form for Bar, then Bar is built by Foo.
I think there's no need to make this kind of validation if you're sure to use Bar only in contexts with Foo.
btw, try to write validation as follow (new preferred syntax for Rails3):
validates :foo, :presence => true
hope this helps,
a.