I am using globalize3 with rails_admin thanks to this gist. What bugs me, is that the user can add as many translations as he wants.
Moreover, he isn't forced to translate the content in every single locale (as in I18n.available_locales). I'd like that. How can you tackle such a situation?
Models (shortened):
class Project < ActiveRecord::Base
has_many :project_translations, :dependent => :destroy, :inverse_of => :project
accepts_nested_attributes_for :project_translations, :allow_destroy => true
class ProjectTranslation < ActiveRecord::Base
belongs_to :project
I ended up using Active Admin plus activeadmin-globalize3 instead. Much easier.
It bugged me too, so I created custom field type that doesn't allow it.
The main class:
module RailsAdmin
module Config
module Fields
module Types
class GlobalizeTabs < RailsAdmin::Config::Fields::Association
RailsAdmin::Config::Fields::Types::register(:globalize_tabs, self)
register_instance_option :partial do
:form_globalize_tabs
end
def method_name
"#{super}_attributes".to_sym
end
# Reader for validation errors of the bound object
def errors
bindings[:object].errors[name]
end
def available_locales
I18n.available_locales
end
def current_locale
I18n.locale
end
# Returns array of Translation objects
# It gets existing or creates new empty translation for every locale
# It's used in fields_for method in partial
def translations
translated_locales = #bindings[:object].translated_locales
available_locales.collect do |locale|
translated_locales.include?(locale) ? #bindings[:object].translation_for(locale) : #bindings[:object].translations.new({ locale: locale })
end
end
end
end
end
end
end
It inherits from RailsAdmin::Config::Fields::Association class, because it uses very similar to _form_nested_many partial (that's used in has_many type).
The partial:
.controls
= form.errors_for(field)
%ul.nav.nav-tabs{ :style => 'margin-top:5px' }
- field.available_locales.each do |locale|
%li{ class: ( 'active' if locale == field.current_locale ) }
%a{ href: "##{locale}", data: { toggle: "tab" } }= locale
.tab-content
= form.fields_for field.name, field.translations, wrapper: false do |nested_form|
.fields.tab-pane{ id: nested_form.object.locale, class: ( 'active' if nested_form.object.locale == field.current_locale ) }
= nested_form.generate({:action => :nested, :model_config => field.associated_model_config, :nested_in => field.name })
= form.help_for(field)
It uses field.translations method from the custom field class, that returns an array of Translation objects.
Every Translation object corresponds to available locale, and it's either an existing object from the database (if translation already exists) or new empty translation object.
E.g.
You've got this available locales:
I18n.available_locales = [:en, :cz, :ru]
You have Page model which includes some translated fields.
Also, you have an object of the class Page (a row in the database), that has translations for :en and :cz locales, but lacks one for the :ru.
So, field.translations method inside _form_globalize_tabs partial returns an array that contains:
2 existing translations for :en and :cz and 1 just initialized translation for :ru.
In the partial I'm passing this array to the fields_for helper method from nested_form gem, that returns 3 fieldsets for every translation object.
You can use this gem, if you don't want to mess with the code yourself: https://github.com/scarfaceDeb/rails_admin_globalize_field
Related
As an example:
def create
resource = build_resource
resource.assign_attributes(params[resource_instance_name], as: :admin)
create! do |format|
format.js {...}
end
end
The problem with above is that attributes are not being assigned with as: :admin, they are being assigned without any check and so this method is not having any effect. Is it the create! method? Attributes are being assigned to this resource elsewhere and I can't find out where it is. Appreciate any insight.
Found my answer here: https://github.com/josevalim/inherited_resources/pull/153. Had to override as_role and role_given? --
def as_role
{ as: current_user.highest_role }
end
def role_given?
true
end
This will then always apply roles to attributes for either the controller it is defined in, or all resources if you inherit from a master resources controller like I do.
I have 2 models like the following
Class Post
has_many :comments, :dependent => :destroy
end
Class Comment
validates_presence_of :post
validates_presence_of :comment
belongs_to :post
end
In Comments controller,
def create
comment = #post.comments.build(params[:comment])
if comment.save
// some code
else
// some code
end
end
When the comment is invalid as per the validation, the comment is not saved. But when the #post object is accessed in the view, it contains a comment object with nil id. This did not happen in Rails 2.3.11. We are upgraded to Rails 3.1 and then now to Rails 3.2. This comment object with nil id disappears when I do #post.reload. We are using REE.
I tried to interchange build and new methods. It had the same result as build. Similar behavior is found across our application. Is it the expected behavior or am I doing something wrong?
This seems like expected behaviour to me.
via http://guides.rubyonrails.org/association_basics.html#belongs_to-association-reference
4.1.1.3 build_association(attributes = {})
The build_association method returns a new object of the associated
type. This object will be instantiated from the passed attributes, and
the link through this object’s foreign key will be set, but the
associated object will not yet be saved.
When you call #post.comments.build(...), Rails:
Creates a new Comment object
sets comment.post_id to #post.id.
Inserts it into the comments array (in memory).
When the validation fails, it doesn't delete the comment and the comment persists in the in-memory comments array. When #post gets to your view, #post.comments still includes that badly validated comment.
As for how to deal with it, I'm not sure. Maybe you could do something like (in your controller)... (Feels pretty ugly though.)
def create
comment = #post.comments.build(params[:comment])
if comment.save
// some code
else
#bad_comment = #post.comments.pop
end
end
I had a similar problem while using rails 3.2
Firstly, you need to create two separate methods in your controller. They will be as follows:
The 'new' method that is used to build your comments using 'build_association'
def new
#post = Post.new
comment = #post.build_comments
end
The 'create' method to actually create your comments using 'create_association'
def create
#post = Post.new(params[:post])
comment = #post.create_comments(params[:post][:comment_attributes])
if comment.save
// some code
else
#bad_comment = #post.comments.pop
end
end
Note: I suggest passing 'comment' attribute as a nested attribute of 'post' through your form using 'fields_for'.
Please refer:
http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
I have an object that I want created once and accessible in one of my models. Where do I put him? I'm afraid if I put him in the model class file he'll get created every time I make a new instance of that model. I only want this object created once at start up. Here's the object:
require 'pubnub'
publish_key = 'fdasfs'
subscribe_key = 'sdfsdsf'
secret_key = 'fsdfsd'
ssl_on = false
pubnub_obj = Pubnub.new(publish_key,
subscribe_key,
secret_key,
ssl_on)
I use him like this in the model:
class Message < ActiveRecord::Base
def self.send_new_message_client(message)
message = { 'some_data' => message }
info = pubnub_obj.publish({
'channel' => 'testing',
'message' => message
})
puts(info)
end
end
In Rails, objects are recreated on each request. If this is some kind of service, it should be a singleton in the scope of a request.
Singleton objects should be created with the ruby singleton mixin:
require 'singleton'
class Pubnub
include Singleton
def initialize(publish_key, subscribe_key, secret_key, ssl_on)
# ...
end
def publish
# ...
end
end
Then you can call it with the instance method:
Pubnub.instance.publish
This way you make sure that this object will actually be a singleton (only one instance will exist).
You can place it safely in the models directory, though I often prefer the lib directory or maybe create a new directory for services. It depends on the situation.
Hope it helps!
If you want only one instance in your whole application, use a singleton, otherwise use a class variable.
To use a singleton, include the Singleton mixin.
require 'singleton'
class Pubnub
include Singleton
attr_writer :publish_key, :subscribe_key, :secret_key, :ssl_on
def publish
#...
end
end
and then use it like this:
require 'pubnub'
class Message < ActiveRecord::Base
Pubnub.instance.publish_key = 'xyz'
Pubnub.instance.subscribe_key = 'xyz'
Pubnub.instance.secret_key = 'xyz'
Pubnub.instance.ssl_on = 'xyz'
def self.send_new_message_client(message)
message = { 'some_data' => message }
info = Pubnub.instance.publish({
'channel' => 'testing',
'message' => message
})
puts(info)
end
end
You could also make it a class variable, to link it more tightly to a specific model:
require 'pubnub'
class Message < ActiveRecord::Base
##pubnub_obj = Pubnub.new('xyz', 'xyz', 'xyz', 'xyz')
def self.send_new_message_client(message)
message = { 'some_data' => message }
info = ##pubnub_obj.publish({
'channel' => 'testing',
'message' => message
})
puts(info)
end
end
In my rails user model, I am trying to write a method which will return a list for the current time frame, and in the absence of a list for that time frame, create one which is associated with the user and then return it:
class User < ActiveRecord::Base
def todaysList
today = Time.new
if self.lists.where(:date => today.to_date)
return self.lists.where(:date => today.to_date).first #Get the object, not the ActiveRecord::Relation
else
self.lists.create!(:date => today.to_date) #Make the list, return it!
end
end
My question is, why is it that when I call self.lists.create!(:foo => 'bar'), the user association is not populated?
I've decided to get around this a more sloppy way, by explicitly assigning the user in the create! call, as such:
self.lists.create!( :date => today.to_date, :User_ID = self.id)
however this solution just doesn't seem right.
Thanks in advance and apologies as always for stupid, redundant or badly worded questions.
I would do something like this:
def todays_list
lists.find_or_create_by_date(Date.today)
end
The method name change is just preference.
I have Rails models User, ReadingList and SessionReadingList. A user has many reading lists. A SessionReadingList is a special type of reading list for before a user has registered, stored in the session.
In my ReadingListsController every action is of the form:
def show
if current_user
#load user's reading lists
else
#load session reading list from session
end
end
I'm wondering whether I'd be better off subclassing ReadingListsController so I have e.g. SessionReadingListsController and UserReadingListsController. I don't know how I'd handle the routing then though.
So, is subclassing the solution? If so, do I redirect from the ReadingListsController depending on current_user? Or is there a better way?
You can create a custom route matcher that uses the appropriate controller.
class LoggedInConstraint < Struct.new(:value)
def matches?(request)
request.cookies.key?("user_token") == value
end
end
match 'reading-list' :to => "reading_list#index", :constraints => LoggedInConstraint.new(true)
match 'reading-list' :to => "session_reading_list#index", :constraints => LoggedInConstraint.new(true)